home *** CD-ROM | disk | FTP | other *** search
Text File | 1978-11-24 | 199.0 KB | 4,741 lines |
- Shadow Development Documentation
- Library Version 5.0
-
- By David Navas
- Updated: 13 Nov 1992
-
- Copyright © 1992 by David Navas
- with help from Karen Lien
- All Rights Reserved
-
-
- THE PLAN
- Basic Concepts
- History
- Overview
- Resource Tracking
- Dual-pass resource tracking
- System String Constants
- Semaphores
- Intra-Process Locking Integrity
- Counting Semaphores
- Conditional Variables
- Multi-Process Signalling
- Performance Issues
- AvlTrees
- Performance Issues
- Summary
- SLists
- Memory
- OOPL
- Object Oriented
- Object-Orientedness under SHADOW
- Creating Classes
- AttributeTags
- MethodTags
- mtag_defnObject
- mtag_procObject
- mtag_flags
- mtag_threadstat
- mtag_priority
- mtag_method
- mtag_arguments
- ArgumentTags
- CreateInstance
- Wrap-up of Methods
- Patcher Class
- Director Class
- Process Class
- Process and Program Shutdown
- Conclusion
-
-
- BASIC CONCEPTS
-
- SHADOW is a concurrent-object-oriented addition to AmigaDOS.
- Its principle design goal is to help standardize an extensible
- environment paradigm. It takes advantage of some of the better
- AmigaDOS facilities (shared memory system, IPC ports, and fast
- context switching) by internally managing much of the inter-task
- communications, resource tracking, and resource allocation.
-
- Traditional object-oriented systems separate function
- interfaces from internal data structures and manage the allocation
- and access of these structures within objects. SHADOW takes this
- interface separation one step further by partially uncoupling the
- parameter specification at method invocation from the parameter
- specification of the method implementation.
-
- Because of this relatively high separation, argument type-
- checking is not implemented under SHADOW. To implement
- type-checking in a run-time bound environment almost requires
- run-time type-checking. It was decided that this was too
- expensive a use of computing resources. Under AmigaOS, there is
- some precedence for this; for example, it is difficult to type-check
- TagItem strutures.
-
- While SHADOW's main goal is to provide a unified interface
- for distributed, extensible, multi-threaded computing, there are
- many provisions for speed, this being just one of them. Other
- examples include callback hooks for method dispatching and attribute
- lookups; if you feel SHADOW's versions are not quite what you want
- in either speed or features or both, you can set these hooks on a
- class-by-class basis. Additionally, attributes are created in such a
- manner as to provide multiple ways of getting at object instance
- variables -- including (but not limited to) the use of more "normal"
- C-structure dereferencing. While this type of dereferencing isn't
- guaranteed if you're using someone else's class (or, even, if you
- are subclassed off of someone else's class), it does provide a
- very fast way of getting at attributes, and SHADOW uses it in
- several places within its internal code for the sake of efficiency.
-
-
- HISTORY
-
- SHADOW was created to solve the problems which I ran into with
- my first programming project -- JazzBench. That experience taught
- me that the most important thing in a co-operative multi-program
- environment is flexibility. You need to be able to change the
- behaviour of EVERYTHING -as- -it- -runs-. This lesson was the
- principle reason behind the initial design of SHADOW, and the result
- of that principle was the entire WatchedVariable construct. To a
- lesser extent, it was also responsible for Patches.
-
- However, that was not the only lesson that was learned. Trying
- to locate governing control within a single server not only created
- bottleneck problems, but also, in the end, either limited or
- complicated the design process for my programs. What I needed was a
- subsystem which effectively dealt with the concurrency and shared
- resource management problems across a distributed environment.
-
- The other major subsystems of SHADOW support the increased use
- of more complicated data structures. Exec lists are very fast at
- some things, but searching and sorting are definitely not their
- strong points.
-
-
-
-
- OVERVIEW
-
- This introduction is an attempt to acquaint the reader with SHADOW's
- capabilities and limitations, and to introduce the programming
- paradigm it offers to Amiga programmers.
-
- The number of functions in SHADOW is truly daunting. However, only a
- very few need to be well-understood in order to program SHADOW
- effectively. These functions fall into about seven categories, which
- we will take up in turn. The following is an informal introduction
- to familiarize you with the terminology and the pertinent function
- calls in SHADOW. Confusing terms can (hopefully) be found in
- Glossary.doc. If they can't be, please let me know what they are, and
- they will appear in future versions of that documentation file.
-
-
-
-
-
- RESOURCE TRACKING CALLS
-
- SHADOW gives programmers the ability to track resources, and
- in fact forces them to do so. Objects are NEVER freed until the
- useCount drops to zero. Objects are often returned from functions
- as Use()'d, meaning that the programmer needs to specifically tell
- the computer when the resource is no longer needed.
-
- Consider the following example:
-
- /*
- * Find an object inside of an avltree.
- */
- myobject = FindTreeStringNode(avltree, MY_OBJECT_NAME);
- .
- .
- .
-
- In this example, a resource that was stored under the
- "MY_OBJECT_NAME" is looked up in a tree and returned to a program.
- Remember that SHADOW is primarily a tool for inter-process sharing
- of objects. At any time, some other program might come along,
- remove your object from your avltree and try and destroy (free)
- that object. To prevent this from happening, the FindTreeNode()
- code (on top of which the FindTreeStringNode() code is written)
- returns the object it finds "UseObject()'d", so that some other
- program cannot pull the object out from under you. It falls to you,
- the programmer, to tell the system when you are done using the
- returned object.
-
- /*
- * Find an object inside of an avltree.
- */
- myobject = FindTreeStringNode(avltree, MY_OBJECT_NAME);
- .
- . <<Read/modify the object>>
- .
- DropObject(myobject); /* No longer interested in myobject */
-
- After the DropObject() call, the myobject pointer should be
- considered invalid, and never used thereafter.
-
- There are several variations on this theme. The examples we
- will cover are as follows:
- 1) Returning objects implies that you must have a a valid
- use()d pointer already, and that the function you return
- to handles the resouce-tracking DropObject() call properly.
- 2) Functions declared as returning void * may, in fact, return
- objects that require them to be resouce tracked.
- 3) Some functions "swallow" resources. DropObject() is one
- example, but there are others.
-
-
- Consider the example code above. Imagine if, instead of
- performing all reads and modifications on the objects between
- the FindTreeStringNode() code and the DropObject() code, we wished
- to return the object to a calling function for further processing:
-
- DoSomething()
- {
- OBJECT myobject;
-
- /*
- * First retrieve and preprocess the object in some
- * manner, then return to me for further processing.
- */
- myobject = getMyLocalObject();
- .
- . <<Read/modify the object a bit here>>
- .
- DropObject(myobject); /* No longer interested */
- }
-
- .
- .
- .
-
- OBJECT getMyLocalObject()
- {
- OBJECT myobj;
-
- /*
- * Find an object inside of an avltree.
- */
- myobj = FindTreeStringNode(avltree, MY_OBJECT_NAME);
- .
- . <<Read/modify the object>>
- .
- return myobj;
- }
-
-
- Notice that getMyLocalObject() does NOT call DropObject() on myobj.
- This is because the resource is still needed when it is returned
- from getMyLocalObject(). The object is truly no longer needed
- only when it is no longer referred to within a sequence of execution.
- Thus, it is DoSomething() that actually calls DropObject(), and not
- getMyLocalObject().
-
- Now consider the DoShadow() call. It is prototyped as returning
- a "void *". Surely many method calls will return other types of data!
- Methods may returns strings, numbers, pointers to structures, objects,
- etc. It is this last case that we are interested in.
-
- DoSomething()
- {
- OBJECT myobject;
-
- /*
- * First retrieve and preprocess the object in some
- * manner, then return to me for further processing.
- */
- myobject = DoShadow(controlObject,
- NULL,
- METH_GET_LOCAL_OBJECT,
- METHOD_END);
- .
- . <<Read/modify the object a bit here>>
- .
- DropObject(myobject); /* No longer interested */
- }
-
- .
- .
- .
-
- /*
- * The method declaration (in part).
- */
- ARG_TAG REF_getMyLocalObject[] = {RET_OBJ};
-
- OBJECT getMyLocalObject(METHOD_ARGS)
- {
- OBJECT myobj;
-
- /*
- * Find an object inside of an avltree.
- */
- myobj = FindTreeStringNode(avltree, MY_OBJECT_NAME);
- .
- . <<Read/modify the object>>
- .
- return myobj;
- }
-
- The method declaration almost exactly parallels the function
- declaration, except that there is an extra ARG_TAG which
- describes what the method expects as arguments and what the method
- returns.
-
- This method declaration will work for any kind of method call.
- Consider a method invoked synchronously across processes. The
- method does not drop one from the usecount of the object and instead
- passes the object pointer back to the calling process. The calling
- process then calls DropObject(). You can see that the resource is
- effectively transferred from one process to another (although that
- procedure is not currently recorded anywhere within these processes).
-
- Consider the asynchronous case. While invoking this method
- asynchronously is of little to no value, it can happen under
- certain error conditions (for instance, if you are invoking a
- synchronous inter-process method from a process without an associated
- SHADOW process object). In this case, the ARG_TAG informs the
- method parsing routines (ParseShadowMessage()) to properly discard
- the returned object. The DoShadow() method invocation returns
- NULL (as with all asynchronous method invocations). DropObject()
- correctly handles the NULL input, however your Read/modify code needs
- to handle this case as well!
-
- One more example of the method invocation is included for
- completeness. Let's say you had a window class that you were
- creating on top of the included ROOT_CLASS. The METH_INIT
- method returns a window object which is not only "Use()d" in the
- local sequence of execution sense, but is also placed on various
- system lists.
-
- OBJECT WinInitMethod(METHOD_ARGS, <window-args>);
- {
- /*
- * Some window initialization.
- */
-
- SetMethodEnd(...); /* Set the METHOD_END in the
- function's argument stack
- correctly for the superclass'
- method. */
-
- return CallSuper();
- }
-
- Thus when you call CreateInstance()...:
-
- windowObj = CreateInstance(NULL, WINDOW_CLASS,
- META_CLASS,
- <window-args>
- METHOD_END);
-
- .
- .
- .
-
- ...you will eventually need to both tell the system not to "use"
- windowObj locally, AND not to use the windowObj anywhere in the
- system lists.
-
- .
- .
- .
- /*
- * No longer need windowObj locally.
- */
- DropObject(windowObj);
-
- .
- .
- .
-
- /*
- * Cleanup code!
- */
- windowObj = getMyLocalWindow();
- RemoveObject(windowObj);
-
-
- RemoveObject() is a linked library call which serves as a
- convenient combination of two function calls. First, a
- METH_REMOVE is sent to the passed object. This informs the
- system to Remove the object from all system lists (for more
- details on METH_REMOVE, see below). The windowObj is then
- passed to the DropObject() function, which removes the
- object from the local sequence of execution. Thus, when
- RemoveObject() returns, the windowObj is no longer a valid
- window pointer, in exactly the same way that it wouldn't be
- valid if passed to DropObject().
-
- RemoveObject() is an example of a function which
- "swallows" an object pointer from the local sequence of execution.
- DropObject() is an example as well. Another example is the
- convenience function SetObject() which transfers ownership of the
- passed object from the local sequence of execution to a shared
- section of memory and returns the object that used to be at that
- memory location back to the program in an atomic fashion.
-
- If, at anytime, you want to retain a local pointer to the object
- that you pass into one of these functions, you may use the code as in
- the following examples:
-
- RemoveObject(UseObject(windowObj);
-
- -or-
-
- oldObject = SetObject(&memoryLocation, UseObject(newObject));
-
- UseObject() increases the usecount of the pointer (and also returns
- the pointer to the object, which allows for the easy, if slightly
- confusing, nesting). At some later point you will have to
- remember to DropObject() the objects, of course!
-
- The following, slightly less confusing, examples do the same thing:
-
- UseObject(windowObj);
- RemoveObject(windowObj);
-
- -or-
-
- windowObj = UseObject(windowObj);
- RemoveObject(windowObj);
-
- -or-
-
- newObject = UseObject(newObject);
- oldObject = SetObject(&memoryLocation, newObject);
-
- etc.
-
-
- In addition to the UseObject() and DropObject() calls for
- SHADOW's objects, there are analogous functions for string
- constants. These string constants, herein referred to as either
- system strings or unique strings, will now be discussed. The
- pertinent calls to resource tracking are UseString() and DropString(),
- which perform reasonably analogously to UseObject() and DropObject().
-
-
- DUAL-PASS RESOURCE TRACKING
-
- The distinction between DropObject()ing a resource and
- RemoveObject()ing is more significant than you might suspect at
- first glance. The main problem with using useCounts in any
- system is the possibility of a self-referencing object, or
- a self-referencing chain of objects. That is, object1
- contains a field which points to object1 -- in which case
- the useCount will never drop to zero; or object1 contains a
- pointer to object2 that contains a pointer to object3 that contains
- a pointer back to object1. All three objects' useCounts, in this
- case, will never fall to zero.
-
- To avoid this problem, SHADOW objects should use the following
- dual-pass algorithm:
- 1) In the METH_REMOVE method for each object, all references to
- other objects should either disappear, or be guaranteed to
- never participate in a self-referencing "ring".
- 2) After a METH_REMOVE method is received by an object, it
- must never CREATE or allow the creation of, a
- self-referencing "ring" in which it is a participant.
- 3) When a METH_DESTROY is sent, the object should be destroyed.
-
- Self-referencing may occur, therefore, only between the
- METH_INIT call to an object, and the METH_REMOVE call.
-
- This allows self-referencing to exist within your programs, but
- defines a procedure for eliminating all of these self-references
- without resorting to something time-consuming like garbage
- collection. The METH_REMOVE pass is often referred to as the
- first pass in the dual-pass resource tracking algorithm, and the
- METH_DESTROY is often referred to as the second pass in the dual-
- pass resource tracking algorithm. It is up to the individual
- programmer to design his or her classes to conform to these
- specifications.
-
- Nearly all of the above refers to the GLOBAL resource tracking
- facilities of SHADOW. SHADOW also provides a LOCAL resource tracking
- facility, which helps to reduce the clean-up code (at the expense of
- some init code). When a resource is allocated that you want to be
- freed when the process exits, you can pass a pointer to the object
- (must be a SHADOW object!) to the AddAutoResource().
- AddAutoResource() is another example of a procedure that "swallows"
- a resource.
-
- If that resource is later sent a METH_REMOVE by your program
- before the program actually exits, you should remove the reference
- to it within the program with RemoveAutoResource(). Please see the
- AutoDocs for more details. The example code may also be helpful,
- although not all of it relies on auto-tracked resources.
-
- When the METH_REMOVE method is sent to your program's process
- object, all local resources that were added (via AddAutoResource())
- are sent a METH_REMOVE, and are then freed from the process' resource
- tree. The RemoveCurrentProgram() sends a METH_REMOVE to
- your program's process, for example.
-
-
- SYSTEM STRING CONSTANTS
-
- SHADOW uses the concept of unique strings as borrowed from the
- NeXT platform. Each string is assigned a unique address by the
- system. Two or more strings of the same contents have the same
- system address. This allows the system to find strings faster --
- by comparing the address, rather than the contents.
-
- The implementation is via the UseString/DropString/FindString
- library functions, as well as the QuickUseString and QuickDropString
- functions. System strings are stored on a 1024 entry hash table
- where collisions are linked by a sorted binary tree. This table is
- found in ShadowBase->sb_systemStrings.
-
- UseString() will lookup the passed string in the table. If it
- does not find it, the string is created and stored into the table.
- Otherwise, the object's usecount of the located system string is
- incremented. The system string's address is then returned.
-
- DropString() will lookup the passed string in the table, and,
- if found, the string's useCount is decremented. If the usecount has
- dropped to zero, the string object is freed and removed from the
- system string list.
-
- FindString() returns the address of a requested system string,
- if one can be found. It will not increment the usecount, and
- therefore should only be used as an address, rather than as an
- actual string pointer, as that string may go away unexpectedly
- at any time.
-
- QuickUseString() and QuickDropString() take known system string
- addresses and manually increments or decrements the usecount. If
- the usecount drops to zero, the normal DropString() is used to
- remove the string from the system. Be sure that you actually
- have a system string pointer before claling these functions! These
- functions exist for performance reasons only. It is preferrable
- to go through the UseString() and DropString() calls instead.
-
- Consider the following:
-
- struct StringData {
- char *sd_name;
- ULONG sd_data;
- };
- struct StringData MyArray[] = {UseString(-blah-), 1,
- UseString(-blah-), 2,
- etc.};
-
-
-
- We can now qsort the array on the sd_name field and lookups can be
- as fast as calling a radix search function!
-
- /*
- * Look up item in qsort()'d MyArray
- *
- * [You can find a decent Radix_Search function in nearly
- * every computer algorithms textbook.]
- */
- Radix_Search(&MyArray, 0,
- sizeof(MyArray)/sizeof(struct StringData),
- UseString(look_for_this_string));
- DropString(look_for_this_string);
-
- .
- .
- .
-
- /*
- * free all the strings.
- * Clean up!
- */
- for(i = 0; i < sizeof(MyArray)/sizeof(struct StringData), i++)
- DropString(MyArray[i].sd_name);
-
-
- Not only does this improve access by making an O(n) process into an
- O(log n) one, but the entire string compare overhead is eliminated
- by calling UseString() once (instead of strcmp() 'n' times)!
-
- System strings are used extensively by the object-oriented subsystem
- for such things as lookups of methods and attributes, and for passing
- strings ('JSTR's) between processes during asynchronous method calls.
- They are also used by the AVL tree subsystem which will be covered
- just as soon as we treat the semaphore subsystem.
-
-
-
- SEMAPHORES
-
- This discussion of SHADOW's semaphores closely follows the
- RKM:Libraries Third Edition's discussion of Exec's semaphores. It is
- assumed in this section that you have either read this chapter or
- learned about Exec semaphores from some other source, and that you
- understand the principle concepts behind semaphores.
-
- Understanding semaphores is a prerequisite to understanding a lot of
- the deeper issues within SHADOW. This is why I treat it very close
- to the beginning of this introduction. If you do not understand
- semaphores, it is highly recommended that you either buy the
- RKM:Libraries book, or some general purpose Operating Systems
- textbook.
-
- Like Exec's semaphoring system, SHADOW's semaphoring system contains
- the basic Shared and Exclusive access constructs. Under Exec, the
- following calls provide these access constructs:
- AttemptSemaphore(<semaphore>)
- ObtainSemaphore(<semaphore>)
- ObtainSemaphoreShared(<semaphore>)
- ReleaseSemaphore(<semaphore>)
-
- The analogous calls in SHADOW are as follows:
- PSem(<address>, SSEM_ATTEMPT | SSEM_LOCK);
- PSem(<address>, SSEM_LOCK);
- PSem(<address>, SSEM_READ);
- VSem(<address>);
-
- Using the macros defined in <shadow/semaphore.h> these become:
- PSem(<address>, SSEM_ATTEMPT | SSEM_LOCK);
- RWLock(<address>);
- ReadLock(<address>);
- VSem(<address>);
-
-
- However, unlike Exec's semaphoring system, SHADOW does not have
- calls that are directly analogous to the AddSemaphore(),
- RemSemaphore(), FindSemaphore() and InitSemaphore() library calls of
- Exec. Instead, SHADOW allows you to semaphore any arbitrary address
- (excepting (void *)-1 which is reserved). The contents at this
- address do not participate in the semaphoring process. This implies
- that there are no SHADOW semaphore structures analogous to Exec's
- SignalSemaphores. Instead, all legal addresses act as global
- semaphore points. SHADOW takes care of creating, initializing, and
- destroying all structures needed to internally manage the semaphoring
- process, and because the Amiga's address space is linear across all
- processes, all semaphores are global by nature. This eliminates the
- need for the AddSemaphore(), the RemSemaphore(), the FindSemaphore(),
- and the InitSemaphore() calls, thus simplifying the programmer's
- task.
-
- Consider two threads running through the following program, one at
- Reader(), one at Writer().
-
- UBYTE *ASharedString;
-
- Reader()
- {
- while(TRUE)
- {
- ReadLock(ASharedString);
- printf("The string %s\n", ASharedString);
- VSem(ASharedString);
- }
- }
-
- Writer()
- {
- while(TRUE)
- {
- WriteLock(ASharedString);
- gets(ASharedString);
- VSem(ASharedString);
- }
- }
-
- "ASharedString" acts as a shared string between the Reader() and
- Writer() threads. To coordinate access to the string, the
- address of the string is also used as the semaphoring address
- point. while this is the most straightforward way of managing the
- semaphore system, there is nothing wrong with creating an arbitrary
- semaphoring address point. For example, you might arbitrate access
- for the string by semaphoring against the Reader()'s function address!
-
- UBYTE *ASharedString;
-
- Reader()
- {
- while(TRUE)
- {
- ReadLock((void *)Reader);
- printf("The string %s\n", ASharedString);
- VSem((void *)Reader);
- }
- }
-
- Writer()
- {
- while(TRUE)
- {
- WriteLock((void *)Reader);
- gets(ASharedString);
- VSem((void *)Reader);
- }
- }
-
- One warning, however. You are not allowed to semaphore on an address
- that you do not in some way possess. This is to guard against the
- possibility that some other process gets started that actually -does-
- use this address. Because SHADOW's semaphoring points are
- automatically public, when this other process attempts to semaphore
- against its address it will come into conflict with your programs.
- This is potentially deadly.
-
- As a corollary, you must release all semaphores that you obtained
- before your program exits and/or before the address that the
- semaphore is arbitrating is freed back to Exec's memory pools.
- To aid in testing, you are directed to the sb_semTree field
- within ShadowBase which acts as the base of all semaphore allocations
- that SHADOW makes. This field is zero when there are no outstanding
- semaphores.
-
-
- In addition to not having the complications of semaphore
- structures and the (unnecessary, on the Amiga) distinction between
- global and local semaphores, SHADOW does not offer Exec's prepackaged
- ObtainSemaphoreList() and ReleaseSemaphoreList(). While these calls
- are sometimes important to use to avoid deadlock situations, it
- is entirely possible to build your own SemaphoreList wrappers by
- clever use of the SSEM_ATTEMPT flag. This is left as the proverbial
- exercise to the reader.
-
- Instead, SHADOW expands on Exec's usage of semaphores to
- include intra-process locking integrity, counting semaphores,
- conditional variables, and multi-process signalling. We take each
- one in turn, and then address performance issues.
-
-
- Intra-Process Locking Integrity
-
- This may be a confusing amalgamation, but the concept is simple.
- Take an example of a library function IterateList(). The purpose
- of IterateList() is to call a function on each of the members on
- a shared list. We might implement this as follows:
-
- IterateList(struct MyList *list, void (*func)(struct MyNode *node))
- {
- struct MyNode *node;
-
- PSem(list, SSEM_READ);
- node = list->first;
- while(node)
- {
- func(node);
- node = node->next;
- }
- VSem(list);
- }
-
- Consider, however, what might happen if we pass a function which
- removed the node from the list it was in. Clearly this would have
- very bad consequences for IterateList()!
-
- Therefore, instead of the normal Exclusive/Shared access locks,
- SHADOW provides three types of access locks -- an SSEM_READ,
- an SSEM_WRITE, and an SSEM_LOCK (a read-write lock). SSEM_READ
- locks are shareable across processes and are nestable within
- processes. SSEM_WRITE is neither shareable nor nestable. SSEM_LOCK
- is not shareable, but is nestable -- with the following modifications:
-
- a) Multiple SSEM_LOCK requests always nest.
- b) If you request an SSEM_READ on an SSEM_LOCKed semaphore,
- the semaphore becomes a nestable, non-shareable,
- SSEM_READ semaphore. The semaphore reverts to its earlier
- Read-Write status upon execution of the matching VSem() call.
- c) If you request an SSEM_WRITE on an SSEM_LOCKed semaphore,
- the semaphore becomes a non-nestable, non-shareable,
- SSEM_WRITE semaphore. Again, the semaphore reverts
- to its earlier Read-Write status upon the matching
- VSem() call.
-
- It is easier to understand this nesting ability by focusing on the
- Read/Write characteristics of these different locks, rather than
- on their Shared/Nestable characteristics.
-
- Any number of processes can Read. Only one process may Write, and
- no one may be Reading at the time. The Read-Write is used for
- mutual exclusion across processes and can become either a Read-only
- or a Write-only semaphore as needed -- although the semaphore will
- always remain non-shareable until the last Read-Write semaphore
- access has been released via VSem().
-
- Consider the following example taken from the RemoveResources()
- function in SHADOW:
-
- .
- .
- .
- PSem(&tree, SSEM_LOCK);
- DoInOrderTree(&tree, RemoveCompInstance, NULL);
- FreeTreeAllNodes(&tree);
- VSem(&tree);
- .
- .
- .
-
- Here we are attempting to eliminate each node from a tree and invoke
- the METH_REMOVE method on each node (object) on the tree. Because of
- the organization of the AVLTree functions (described below), this is
- a two stage process -- we invoke the METH_REMOVEs on all objects in
- the tree (DoInOrderTree()), and then remove all objects from the
- tree (FreeTreeAllNodes()).
-
- As in the above list example, the DoInOrderTree() library function
- locks the tree with an SSEM_READ semaphore call. The
- FreeTreeAllNodes(), on the other hand, locks the tree with an
- SSEM_WRITE semaphore.
-
- In order to be truly safe, we need to ensure that no other process
- adds a node between the DoInOrderTree() call and the
- FreeTreeAllNodes() call where the tree would normally be accessible.
- To accomplish this, we get an SSEM_LOCK semaphore to surround the two
- function calls. The SSEM_LOCK provides the nesting that is needed by
- the two library functions which acquire their own semaphores, while
- maintaining the exclusive access characteristics required by the code.
-
- In addition, because SSEM_READ/SSEM_LOCK nesting is different
- from SSEM_LOCK/SSEM_LOCK nesting, the intra-process locking integrity
- is maintained. Any attempt to gain write or read-write access to the
- tree from within the DoInOrderTree() call will halt the program, and
- any attempt to lock the tree at all within the FreeTreeAllNodes()
- will halt the program. This allows the programmer to debug the
- problem when it occurs, instead of trying to deduce what occurred
- from problems which might have normally ensued.
-
- Compare that to the alternative as suggested by V39Beta Exec
- semaphores. Not only does Exec fail to make a distinction
- between Write and Read-Write semaphores, but when you request
- a shared semaphore on a semaphore that you already have locked
- exclusively, you get another exclusive access granted! This
- precludes the potential debugging advantages of SHADOW's semaphores.
-
- [Of course, it's even worse when you consider that V37 semaphores
- did something entirely different under this exact same set of
- circumstances anyway....]
-
-
- Counting Semaphores
-
- Counting Semaphores are a very straightforward addition to most
- semaphoring systems. A typical example is with resource
- allocations. Consider the problem of an audio.device type interface
- where you have four available channels. All four channels are
- allocated, and a program is waiting for any two.
-
- Here is where counting semaphores come in. You create a
- counting semaphore by using the CreateSemaphore() macro, passing
- in the maximum number of available resources. It returns the
- semaphoring address point that you can use in the rest of
- your calls. You should later free this count semaphore by
- calling DestroySemaphore(), passing in the same address as
- was returned by CreateSemaphore. The ObtainNumber() and
- ReleaseNumber() macros can be used on this address between
- the Create() and Destroy() calls to obtain and release a
- certain number of resources.
-
- APTR GlobalResourceSemaphore = CreateSemaphore(4);
-
- .
- .
- .
-
- /* Obtain two channels. */
- ObtainCount(GlobalResourceSemaphore, 2);
-
- .
- .
- .
-
- /* release a channel back to the system */
- ReleaseCount(GlobalResourceSemaphore, 1);
-
- .
- .
- .
-
- /* Obtain three channels */
- ObtainCount(GlobalResourceSemaphore, 3);
-
- .
- .
- .
-
- /* Prepare to destroy the semaphore */
- DestroySemaphore(GlobalResourceSemaphore);
-
- /*
- * All resources have been freed, semaphore is now freed
- * as well.
- */
- ReleaseSemaphore(GlobalResourceSemaphore, 4);
-
-
- Notice carefully that you can ask for any number of resources --
- if you ask for too many, you may hang indefinitely, and as there
- is no priority to your semaphore requests, starvation is definitely
- possible.
- Also note that the semaphore is not actually freed from the system
- until all processes release their locks and no process is
- waiting on the semaphore, irregardless of the call to
- DestroySemaphore().
-
- In addition to these calls, you can call the UpCountSem() and
- DownCountSem() macros which create the semaphore automatically
- from the passed address. This eliminates the need to call the
- CreateSemaphore() and DestroySemaphore() macros, but it limits you
- to single-step increment/decrement calls.
-
-
- ULONG SemaphorePoint;
-
-
- /* Obtain two channels of four. */
- UpCount(&SemaphorePoint, 4);
- UpCount(&SemaphorePoint, 4);
-
- .
- .
- .
-
- /* release a channel back to the system */
- DownCount(&SemaphorePoint);
-
- .
- .
- .
-
- /* Obtain three channels of four*/
- UpCount(&SemaphorePoint, 4);
- UpCount(&SemaphorePoint, 4);
- UpCount(&SemaphorePoint, 4);
-
-
- DownCount(&SemaphorePoint);
- DownCount(&SemaphorePoint);
- DownCount(&SemaphorePoint);
- DownCount(&SemaphorePoint);
- /*
- * All resources have been freed, semaphore is now freed
- * as well (automatically).
- */
-
-
- Conditional Variables
-
- Conditional Variables keep track of the number of "conditions" that
- have occurred. A task can cause any number of conditions, and can
- wait for any number of conditions.
-
- Consider the Reader() and Writer() problem above. Consider what is
- really happening. It is quite possible for a single string to be
- printed out between zero and -thousands- of times. In reality, what
- we want is to have each string printed out once. Here, the Reader()
- and Writer() example becomes the more classic Consumer() and
- Producer() problem. Here is an example implementation using
- conditional variables:
-
- extern void Producer(void);
-
- UBYTE *ASharedString;
-
- void main(void)
- {
- SetCondition(((char *)Producer) + 1, 1)
- /*
- * Default to produce the first string.
- * We can obviously default to consuming the
- * first string as well....
- */
-
- startpThreads();
- }
-
-
- void Consumer(void)
- {
- while(TRUE)
- {
- WaitCondition(((char *)Consumer) + 1, 1);
- /*
- * Wait for a new string.
- */
- printf("The string %s\n", ASharedString);
- /*
- * Print the new string
- */
- SetCondition(((char *)Producer) + 1, 1);
- /*
- * The string has been consumed, want another one!
- */
- }
- }
-
- void Producer(void)
- {
- while(TRUE)
- {
-
- WaitCondition(((char *)Producer) + 1, 1);
- /*
- * Wait for a need for a new string.
- */
- gets(ASharedString);
- /*
- * Get another string.
- */
- SetCondition(((char *)Producer) + 1, 1);
- /*
- * The string has been produced!
- */
- }
- }
-
- This code will work with as many Producer and Consumer threads as
- you like. Only one condition occurs, so only WaitCondition()
- is satisfied for each SetCondition() call. Also, the startup
- case assumes that no string is, at first, available. The code
- would be slightly different without that assumption.
-
- Also notice that these programs use -odd- addresses for condition
- variables. This is to avoid potential address collisions. Most
- lock functions occur at even addresses, so I have all of my
- programs use the odd addresses for conditional varialbes, thereby
- eliminating a potential source of confusing. There is NO dictum
- that requires you to follow in my footsteps, though.
-
-
- Multi-Process Signalling
-
- Multi-Process Signalling allows an arbitrarily large number of
- programs to find out when something happens. Where condition
- variables keep track of the number of conditions that occurred
- but haven't been handled, the Multi-Process signalling
- cannot keep track of the number of "outstanding" conditions
- that have occurred. However, it can wake up EVERYONE
- waiting on the WaitLevel() macro.
-
- WaitLevel(address); waits for a condition to occur associated
- with the passed address.
-
- SetLevel(address); makes the multi-process signalling
- condition occur.
-
- Examples are left as an exercise for the reader.
-
-
- Performance Issues
-
- Considering the extended number of features that SHADOW
- includes, and especially considering that the vast majority of
- semaphores are created and deleted on the fly, it may not be too
- surprising to discover that SHADOW's semaphoring system is slower
- than Exec's. Indeed, it would be miraculous if it weren't. What
- is surprising is that SHADOW's semaphores are very competitive with
- Exec's, and if we reduce every exec semaphore access to include a
- FindSemaphore() call (a Find() on a string that is less than four
- byteslong, even), we find that SHADOW's semaphores run at about
- the same speed as Exec's semaphores.
-
- To be more precise, SHADOW's semaphores have a performance
- overhead of about 2-5x that of Exec's local semaphoring system (that
- is, Exec's semaphores without the overhead of FindSemaphore()) and
- about the same overhead as Exec's global semaphoring system (that is,
- Exec's semaphores with the FindSemaphore() overhead).
-
- EXEC's global semaphoring system has a frightening overhead when
- either large strings (more than four characters) or large numbers of
- semaphores are used. For instance, if each string is about sixteen
- characters long instead of four, Exec does only about half as well as
- SHADOW. Because SHADOW uses addresses as a semaphoring point,
- instead of strings, and because SHADOW stores its semaphores on a
- binary tree, instead of a linked list, SHADOW's overhead grows
- slower than Exec's. The test cases sited here are performed in the
- included "perftest" program, and uses only 12 semaphores at a time --
- a reasonable figure, it seems to me.
-
- But what if you want to use SHADOW's semaphores and key the
- semaphores on a string? Use the PSemString() and VSemString() calls!
- Essentially, the string is UseString()d by the system, and the
- address of the returned string is used as the semaphoring point.
- This allows for both the flexibility of named semaphores, while
- allowing for the speed of unnamed semaphores.
-
- A good use for named semaphores is for coordinating the startup
- of a program. Consider what happens if two copies of the same program
- get launched nearly simultaneously. Because each program defines
- the same classes to the system (usually), it is wise to start each
- program only once, and then merely instantiate another existence
- of the program once started. This provides the flexibility of
- "resident" programs, without the constant overhead for programs
- that aren't needed.
-
- Unfortunately, it is difficult to coordinate the simultaneous
- startup of programs. Browser uses the following code:
-
- PSemString("Browser Program", SSEM_LOCK);
- /*
- * CreateInstance() only works if the BLOCK_CLASS is already
- * defined (which means that another browser program has been
- * launched).
- *
- * Otherwise, we're the first browser program, and will have to
- * define BLOCK_CLASS, etc.
- */
- if (CreateInstance(NULL, BLOCK_CLASS, META_CLASS, METHOD_END))
- {
- VSemString("Browser Program");
- return;
- }
- .
- . /* define BLOCK_CLASS, etc. */
- .
- VSemString("Browser Program");
- .
- .
- .
-
- Here we see that if two Browser Programs startup, the code is
- guaranteed to define the BLOCK_CLASS in one program, and then
- instantiate an instance of BLOCK_CLASS in the other.
-
- --
-
- Getting back to performance issues, another advantage to SHADOW's
- semaphores is that they take less space than their Exec counterparts,
- first because they don't require the overhead of semaphore-lists, and
- second because the semaphores don't need to exist in memory until
- needed. It is worth noting that SHADOW will pseudo-busy wait if it
- tries to allocate a semaphore structure but runs out of memory,
- unless, of course, it was passed the SSEM_ATTEMPT flag -- in which
- case it fails.
-
- So we conclude that while Exec semaphores have some speed
- advantage in the simple cases, they are slower when sharing semaphores
- across more than one program, always take more memory, and are more
- difficult to setup and use. I definitely recommend the SHADOW
- subsystem over the Exec subsystem, if only for the flexibility that
- it offers unless speed is absolutely crucial.
-
-
-
- AVLTREES
-
- AVL trees are auto-balancing binary trees. Again, this
- discussion assumes that you have read a decent algorithms book which
- would cover AVL trees. I suggest "Data Structgures and Program
- Design in C" by Robert L. Kruse, Bruse P. Leung, and Clovis L. Tondo.
-
- SHADOW's AVL trees are built on top of the semaphore code, the
- memory code (discussed below), the resource tracking code, and, in
- some part, the system string code. They are designed for quick
- access by many processes and are used to store metas, classes,
- and other instances within SHADOW. Again, the design goal of SHADOW
- is to provide for multi-threaded computing, so all AVL trees are
- semaphored when accessed by the system.
-
- To initialize an avl tree:
-
- AVLTree bt = NULL;
-
- Now you can add any number of SHADOW-objects (the definition of
- exactly what a SHADOW-object is is located below) to the binary tree.
- Indeed, you may add any number of objects with any key to any number
- of binary trees. AVLTrees usually require you to have different
- keys for each inserted item. SHADOW does not impose this
- restriction. Exec requires you to have one Node structure for
- each list that an "object" might belong to. Again, SHADOW does
- not require this. SHADOW allocates and frees its own BinNodes
- internally when required. In addition, to support the idea of
- non-unique keys, the RemoveTreeNode() function takes the key AND
- the address of the object you want removed -- if you don't know the
- address of the object, you can find it by calling FindTreeNode()
- with the key. The first object found with that key will be returned,
- and you may pass this to the RemoveTreeNode() function.
-
- OBJECT object1 = <>, object2 = <>;
-
- AddTreeNode(&bt, object1, 12);
- AddTreeNode(&bt, object2, 13);
- /*
- * Note: object1 and object2 have not been swallowed,
- * so swallow them now!
- */
- DropObject(object1);
- DropObject(object2);
-
- .
- .
- .
-
- obj = FindTreeNode(&bt, 13);
- /*
- * do something useful with obj!
- */
- DropObject(obj);
-
- .
- .
- .
-
- /*
- * Now we remove all of our nodes from the tree.
- */
- obj = FindTreeNode(&bt, 12);
- RemoveTreeNode(&bt, obj, 12);
- DropObject(obj);
- obj = FindTreeNode(&bt, 13);
- RemoveTreeNode(&bt, obj, 13);
- DropObject(obj);
-
- /*
- * Alternatively, we can call FreeTreeAllNodes()
- * providing we meet the following specifications:
- *
- * All nodes that were added must have been added to
- * the tree via the Add*TreeStringNode() functions.
- *
- * All nodes not added this way must have keys
- * between 0 and 255 or keys equal to the object's
- * address [ie: AddTreeNode(&bt, obj, (ULONG)obj)].
- *
- * FreeTreeAllNodes(&bt);
- *
- * This removes all nodes from the tree and drops all
- * the applicable resources.
- */
-
- SHADOW provides functions which sort the objects by a string. Do
- not fool yourself into believing that this produces an object tree
- which is sorted alphabetically -- SHADOW's string routines are built
- around the system strings mentioned above. Sorting of the nodes
- occurs according to the -address- of the system string, NOT the
- contents of the string itself.
-
- /*
- * Add some objects keyed on some strings.
- */
- AddTreeStringNode(&bt, object1, "Sort me!");
- AddTreeStringNode(&bt, object2, "Me too!");
- DropObject(object1);
- DropObject(object2);
-
- .
- .
- .
-
- /*
- * We now demonstrate both lookups of objects using
- * a string as a key, and removal of objects from
- * trees where the object was keyed on a string.
- */
- object1 = FindTreeStringNode(&bt, "Sort me!");
- object2 = FindTreeStringNode(&bt, "Me too!");
- RemoveTreeStringNode(&bt, object1, "Sort me!");
- RemoveTreeStringNode(&bt, object2, "Me too!");
- DropObject(object1);
- DropObject(object2);
-
-
- SHADOW also provides a routine which will call a function on each
- node within the bintree -- passing in the node, the key, and a
- value passed into the RecurseTree() function.
-
- /*
- * Example RecurseTree() callback.
- *
- * Here we are looking for something inside an object
- * that matches the passed "passedIn" variable.
- *
- * We will return the object if found, or NULL to
- * continue looking.
- * The Recurse() function then returns what we
- * return, or NULL if we never return anything.
- */
- void __regargs *myFunc(OBJECT obj, ULONG key,
- void *passedIn)
- {
- struct SomePrivStruct *sps;
-
- sps = FindAttribute(obj, ATTR_PRIVATESTUFF);
-
- if (sps->sps_searchMe == passedIn)
- return UseObject(obj); /* return object used! */
-
- return NULL;
- }
-
- .
- .
- .
-
- /*
- * Find the object that has the correct info inside it.
- * Search all objects on the binary tree -- we don't
- * particularly care about the order.
- *
- * The number 67 is passed to the myFunc callback as
- * the "passedIn" variable of that function.
- */
- found = RecurseTree(&bt, myFunc, (void *)67,
- SHADOW_RECURSE_INORDER);
- .
- .
- .
- DropObject(found);
-
- Here we have demonstrated a search implemented via the RecurseTree().
- Using this function we can also duplicate an entire tree:
-
- /*
- * We assume that the binary tree had all of its objects
- * stored according to a system string object.
- * Adjustments would have to be made if this were not so.
- */
- void __regargs *dupStringFunc(OBJECT obj, ULONG key,
- AVLTree *passedIn)
- {
- AddTreeStringNode(passedIn, obj, (char *)key);
- /*
- * Remember to return NULL to continue the
- * recursion!
- */
- return NULL;
- }
-
- .
- .
- .
-
- /*
- * Duplicate the avl tree.
- * bt gets duplicated onto bt2. Don't forget to set bt2
- * to NULL to start with!
- */
- bt2 = NULL;
- RecurseTree(&bt, myFunc, &bt2, SHADOW_RECURSE_INORDER);
-
- /*
- * bt2 now has a direct copy of bt.
- */
-
- .
- .
- .
-
- /*
- * Cleanup!
- */
- FreeTreeAllNodes(&bt);
- FreeTreeAllNodes(&bt2);
-
- There are several different ways to recurse down the binary tree:
- SHADOW_RECURSE_PREORDER
- SHADOW_RECURSE_INORDER
- SHADOW_RECURSE_POSTORDER
- SHADOW_RECURSE_BACKORDER
-
- PREORDER visits the nodes as Root, left-tree, right-tree. INORDER
- visits the nodes as left-tree, Root, right-tree -- giving an
- increasing key traversal. POSTORDER visits the nodes as left-tree,
- Root, right-tree. BACKORDER visits the nodes as right-tree, Root,
- left-tree -- giving a decreasng key traversal.
-
- Several macro functions have been provided in much the same way that
- macros are available for many of the different PSem() semaphore calls.
- These are:
- DoPreOrderTree(&bt, func, passedIn)
- DoInOrderTree(&bt, func, passedIn)
- DoPostOrderTree(&bt, func, passedIn)
- DoBackOrderTree(&bt, func, passedIn)
-
- They directly correspond to the flags of similar name.
-
-
- There are several important things to remember when using SHADOW's
- AVL trees. For one thing, some of the functions assume that the
- nodes are sorted by system string address (FreeTreeAllNodes()),
- and won't be happy if they find out otherwise. Other functions,
- like RecurseTree() don't care, but the callback functions supplied
- to this call might.
-
- In addition, it is important to remember that all nodes are sorted
- on unsigned keys, not signed keys. Also, all objects added to avl
- trees must be SHADOW objects, not some concoction of your own.
- This either means that the first three longwords correspond to the
- struct ClasslessObject definition, or that the object was
- created via a CreateInstance()/CreateSubClass() or equivalent call.
-
-
- Performance Issues
-
- SHADOW's AVL tree functions (apart from the *Watched*() functions
- which are really a part of another subsystem) are implemented in
- assembly. They are guaranteed to use less than 256 bytes of stack,
- independent of how large the tree grows -- assuming the tree resides
- in a 32bit address space, that is.
-
- Despite the relatively large overhead implied by the use of SHADOW's
- semaphores, a node can be found in a 256 node tree in an average of
- 84 microseconds on my A3000 -- an object stored with a string as
- the key would take slightly longer, depending on the length of the
- string.
-
- Adding and removing nodes takes slightly longer. Again, with an
- average of 256 nodes inserted and removed in order, the total
- add-remove cycle takes about 280 microseconds (on the A3000).
- This is not bad, considering that the binary nodes used internally
- by the AVLTree subsystem are allocated and freed as needed. Exec
- lists, while they do not have this assorted cost, are less flexible
- about how many times an "object" can be added and to how many trees.
-
- You should pick the storage structure that is best suited for you.
- Fortunately, SHADOW now gives you a choice, without forcing you to
- do your own implementation.
-
-
- Summary
-
- In summary, there are only four really important AVLTREE calls to
- understand:
- AddTreeNode() adds an object to an AVL tree sorted by a
- passed key.
- RemoveTreeNode() removes an object from the AVL tree.
- FindTreeNode() finds the object in an AVL tree, and returns
- this object. You should DropObject() that
- object when you are done using it, as is TRUE
- of any resource that SHADOW returns to you.
- RecurseTree() calls a function for every object in the binary
- tree.
-
- The other function which is NOT a derivative of these four is
- FreeTreeAllNodes(). It's a pretty esoteric call -- it just removes
- all objects from the AVL tree, though. Nothing special. [Note
- the restrictions on its use!]
-
- All other AVL tree calls are derivatives of the first three calls,
- and four other calls [DoPreOrderTree() for example] are built
- directly on top of RecurseTree() as #defines macros.
-
-
- SLISTS
-
- SLISTs are single-linked, prioritized lists. The salient
- functions are:
- AddSListNode()
- RemoveSListNode()
- FindNodePriInSList()
-
- Like their SHADOW avltree cousins, these functions are semaphored and
- the objects added into the lists are resource-tracked -- so all
- objects added to these lists MUST be SHADOW-objects.
-
- The only advantages they add over Exec is the resource tracking, the
- automatic semaphores and the ability for objects to reside in
- multiple lists, multiple numbers of times. The real disadvantage is
- that there is a great deal of overhead for adding prioritized
- nodes, and removing nodes.
-
- Use them if you find them useful.
-
-
-
- MEMORY
-
- In this discussion about semaphores and AVLTrees and SLists, we
- have glossed over one consistent point -- memory management. For
- instance, we know that SHADOW's semaphores have some kind of
- internally managed semaphore object, we know that AVLTrees have some
- kind of internally managed BinNode structures, and we know that
- SLISTs have some kind of internally managed Node structures, but we
- don't know how these structures are allocated and freed.
-
- SHADOW has a memory subsystem that is responsible for dealing with
- these tasks. While its main purpose is to create and destroy these
- small structures as fast as possible, it has enough hooks to be
- useful to anyone who has to manage a large number of structures of
- exactly the same size.
-
- The following are the functions which implement SHADOW's MEMORY
- subsystem:
- result = (BOOL)InitTable(SEMLIST list, allocfunc, freefunc, size)
- memory = (void *)AllocateItem(SEMLIST list)
- (void)FreeItem(SEMLIST list, (void *)memory)
- FreeTable(SEMLIST list)
-
-
- You use these memory tables by first calling the InitTable(),
- using the AllocateItem() and FreeItem() calls, and then calling
- FreeTable().
-
- struct MemoryList globalMemList;
-
- InitTable(&globalMemList, NULL, NULL, 16);
-
- This initializes the memory table to use the default allocation and
- free calls (AllocMem(MEMF_PUBLIC)/FreeMem()). In addition, it sets
- up the memory table to dole out memory in 16byte chunks, and
- pre-allocates 32 items.
-
- Each item allocated from the table will be 16bytes long. The contents
- of the items are not guaranteed to be initialized to zero, EVEN if you
- use your own function callbacks to allocate the memory as MEMF_CLEAR.
- Items are allocated from Exec's memory pools 32chunks at a time.
- When enough items are put back into the table, the table will free
- a full 32chunks back into Exec's free memory pool. The definition
- of "enough" is purposely left vague.
-
- memory = AllocateItem(&globalMemList);
- FreeItem(&globalMemList, memory);
-
- When all allocations are finished and you are done using the memory
- list, call FreeTable(). This frees all the still allocated items
- that you might have left allocated, and it returns all resources
- to Exec's free memory pool.
-
- memory = AllocateItem(&globalMemList);
- .
- .
- .
- FreeTable(&globalMemList); /* All outstanding allocations are
- returned to Exec */
-
-
- Here are the default allocmem() and freemem() callbacks that SHADOW
- uses to allocate and free memory from Exec. These functions are
- called everytime a new chunk of 32items is needed from Exec.
-
- void * __regargs temporaryAlloc(struct MemoryList *list)
- {
- return AllocMem(32 * list->memlst_size +
- sizeof(struct MemoryNode),
- MEMF_PUBLIC);
- }
-
- void __regargs temporaryFree(struct MemoryList *list,
- struct MemoryNode *node)
- {
- FreeMem(node, 32 * list->memlst_size +
- sizeof(struct MemoryNode));
- }
-
-
- Again, because SHADOW is principly designed for multi-threaded work,
- these memory lists use semaphores for each AllocateItem() and
- FreeItem() call. If you don't need this protection, set the
- memlst_semUse to FALSE, and the EXEC Signal Semaphore that is a part
- of the MemoryList structure will not be used. [You should set
- the semUse flag AFTER the InitTable() is called, and not before!]
-
- The relative performance of SHADOW is hard to measure because Exec's
- memory allocation and freeing routines can differ by a factor of
- five. However, SHADOW's memory allocation routines, even
- WITH the semaphore, are about as fast as I've ever seen Exec's
- routines. Without the semaphore they are obviously faster.
-
- What is plainly (painfully!) obvious is how bad Exec's routines
- degenerate when frees are perfectly interleaved (that is, when
- all of the odd numbered allocations are freed first, and then
- all the even numbered allocations are freed). SHADOW is
- faster in this case by, at least, a factor of ten!
-
- Obviously, if the semaphore is not needed, SHADOW's memory routines
- are much faster than Exec's memory routines. No attempt has been
- made to compare SHADOW's routines with anything other than Exec's
- AllocMem() and FreeMem() calls. It is possible that some of Exec's
- other routines would be faster (in certain cases) than SHADOW's.
- It is up to the programmer to decide which set of functions best
- suits his or her purpose.
-
-
-
- OOPL
-
- Now that you know a little about the functional interface to the
- library, it is high time to introduce the object-oriented interface.
- However, this seems to be a good time to mention what SHADOW is NOT.
- SHADOW is not an Object-Oriented Programming Language (OOPL). It is
- also not a Concurrent-Object-Oriented Programming Language. Indeed,
- it is not a language at all. It is entirely possible to envision a
- language built around SHADOW at some point in the future. For right
- now, however, SHADOW is accessed via the 'C' language.
-
- So why not have built around an existing object-oriented language?
- True, the addition of semaphores, avl-trees, system strings, and
- even (with a bit of work) resource tracking, would all be possible
- under a language such as C++. However, rewriting C++'s method
- dispatcher to provide for the synchronous and asynchronous method
- sends would have proved difficult, as C++ doesn't have one.
-
- Indeed, the languages that do exist which have concurrency syntax
- are usually built not only as "concurrent" languages, but as
- "distributed, concurrent" languages. This type of abstraction
- is not necessary on the Amiga as the Amiga has only a single
- "process-space". And yes, this implies you can't magically
- hook up two Amigas with SHADOW and expect things to work
- twice as fast together (or together at all at whatever speed).
- Remember, the design goal of SHADOW was to take advantage of
- the Amiga's better facilities -- principally among them, low
- overhead task switching and shared memory, not to add
- functionality.
-
-
- Object Oriented
-
- So what is object-oriented programming anyway? The classical
- definition of object-oriented programming is by example --
- ie: object-oriented programming was introduced to the world as a
- brand new way of programming, but the only formal definition
- offered was the example language implementations at the time
- (Smalltalk being the better known of these). This is an unacceptable
- definition. Unfortunately, the term has grown to mean a number
- of things to a number of people.
-
- However, there are some general "philosophies" behind Object-Oriented
- Programming. Object-Oriented Programming attempts to model the
- world as a collection of "things" where these "things" have
- certain "characteristics" and are capable of performing certain
- "actions". For instance, dogs are things that have certain hair-
- color, height, weight, etc. Dogs can also do things like "bark",
- "eat", "sleep", etc.
-
- In addition to this, many object-oriented languages draw the
- distinction between definitive "things" and indefinite "things".
- For example, "-a- dog" is an example of an indefinite thing,
- whereas "-the- dog Rover" is an example of a definitive thing.
- As SHADOW makes this distinction this is not of purely
- academic interest.
-
- Object-Oriented Programming offers some names for the above, however
- while many of the names are consistent, some of them are not. We
- will use the terminology that SHADOW uses. if you wish to
- understand SHADOW against the wider diversity of object-oriented
- systems, you'll have to do some more reading.
-
- Under SHADOW, "things" are referred to as objects. Often you will
- see the term in uppercase as in "OBJECTs" -- this is because
- "OBJECT" is an actual 'C' defined type (typedef), there is no
- difference here. Some later distinction is drawn between "Object"
- and "object," but that is immaterial to this discussion. Under
- SHADOW, indefinite things are objects, definite things are objects,
- everything is an object.
-
- An indefinite object is referred to as a class (or, alternatively in
- some of the docs, as a "cob_class"). A definite object is called
- an instance. A definite object is always an instance of a particular
- corresponding indefinite object. For example, "Rover" is an
- instance of "dog":
-
-
- Real World Object World Description
- ---------- ------------ -----------------
- DOG -- class -- indefinite object
- Rover -- instance -- definite object
-
-
- In addition, these object have physical "characteristics" and they
- possess the ability to perform "actions". Under SHADOW, these
- charateristics are referred to as attributes, and the actions are
- referred to as methods. In addition, attributes and methods are
- both -described- in a class, not in an instance. For example,
- when we model a dog in the computer system, we define certain
- characteristics and actions that dogs, in general, will have
- and perform. A dog has a certain hair color, weight, height,
- and they all know how to bark, eat, and sleep. Rover, as an
- instance of this imaginary dog-class, automatically would contain
- the space for its characteristics, and could call the "bark",
- "eat", and "sleep" routines that were specified in the class.
-
- We can imagine some simple code that illustrates this:
-
- new CLASS called DOG {
- Attributes:
- weight is a float
- height is a float
- hair-color is a set containing (black, brown, white)
-
- Methods:
- bark:
- play_sound "dh0:samples/barkingDog"
- end
- eat:
- say "Munch, munch, slurp, slurp, munch, munch."
- end
- sleep:
- display "dh0:pictures/SleepingDog.pic"
- end
- }
-
- new INSTANCE called ROVER of CLASS DOG {
- weight = 13.2
- height = 33.6
- hair-color is (brown, black)
- }
-
- At this point you can do something like:
- ROVER bark
-
- and ROVER would 'play_sound "dh0:samples/barkingDog"'.
-
- Obviously, while the syntax is different, there hasn't been much
- gained over straight 'C' here. But we aren't quite finished yet!
-
- There is yet one more feature to Object-Oriented Programming, and
- that is the notion of "subclassing". Subclassing is the heart
- of "reuse' -- the notion that old code never dies, it just gets
- recycled. Let's take the same dog example again. Dogs are nice,
- for some people, but others prefer a particular -subclass- of
- dogs, say, hunting dogs. A hunting dogs do useful things like
- retrieving dead ducks from slimey swamp pits. So, we have the
- following pseudocode:
-
- new CLASS called HUNTING_DOG as SUBCLASS of DOG {
- methods:
- retrieve:
- exists duck?
- print "Dog now has duck"
- else
- object bark
- end
- }
-
- new INSTANCE called SPOT of class HUNTING_DOG {
- weight = 15.2
- height = 30,5
- hair-color is (black, white)
- }
-
- Notice that the HUNTING_DOG instance SPOT can specify all the
- attributes that ROVER did. This is because HUNTING_DOG "inherits"
- the attribute definitions from its "superclass", DOG.
-
- In addition, if there was no duck, and you did the following:
-
- SPOT retrieve
-
- SPOT would bark (else... object bark)! Just as HUNTING_DOG
- inherits attributes, it inherits the methods of DOG as well.
-
- There is one more possibility:
-
- ROVER retrieve
-
- Remember, ROVER is not a HUNTING_DOG. ROVER, therefore, does not
- know how to retrieve Under SHADOW, nothing happens (an error
- code is set in your process object, though). In C++ this causes
- a compile-time error, and in Objective-C it causes a program
- failure ("Unimplemented Method"). SHADOW is a bit more forgiving
- than either of these languages....
-
-
- Object-Orientedness under SHADOW
-
- That taken care of, you can imagine that creating class descriptions,
- in particular the descriptions of attributes and methods of a new
- class, in 'C' can be quite challenging. And I'm afraid that you
- would be correct in that assessment.
-
- The rest of this document focuses on the issue of creating new
- classes and describes the inter-relationships of the classes
- provided to you by SHADOW.
-
- The document 'ShadowLibraryMethods.doc' introduces SHADOW's
- object-oriented system. Excepting some brief reiterations
- and clarifications, that document will continue to contain
- the main introduction to the object-oriented system. Therefore,
- it is not merely highly suggested that you read the introduction
- in ShadowLibraryMethods.doc, it is prerequisite that you do so.
-
- To reiterate, aside from the functional aspects of SHADOW which
- are introduced above, everything under SHADOW is an object. All
- SHADOW objects begin with a struct CoreObject which can be found in
- <shadow/core.h>. There are two fields in this structure, the
- cob_useCount and the cob_class. The cob_useCount is used by the
- resource tracking system and is covered earlier in this document.
- The cob_class points to another OBJECT which contains the
- information that is necessary to create, destroy, and otherwise
- maintain this object. In short, this description (or blueprint or
- class), contains a description of the attributes (or properties) of
- the object and the methods (or functions, or verbs) of the object.
-
- The relationship between "object" and "cob_class" is called the
- "instantiation hierarchy". Object-oriented systems typically
- describe classes in relation to other classes. The former group
- of classes are called subclasses, and the latter are called
- superclasses.
-
- In the case of the "superclass hierarchy", there is a class which
- has no superclass. This class is typically referred to as the
- rootclass. In the case of SHADOW's "instantiation hierarchy", there
- is no object without a cob_class, after all, an object without
- a description is pretty hard to create! However, there is a
- descriptor which, instead of terminating the instantiation
- hierarchy, is a descriptor for itself. These descriptions
- are referred to as METAs.
-
- The instantiation hierarchy for SHADOW is three deep. For a summary,
- please refer to the "instantiate hierarchy" entry in the Glossary.doc.
-
- This three-level setup allows you, the programmer, to change
- the methods of both your objects (by changing the method
- descriptions in your classes) -and- your classes (by
- changing the method descriptions in your metas).
-
- There is more about METAs in ShadowLibraryMethods.doc, and I suggest
- you read about them there as well. This document is more concerned
- with how you actually create classes and metas, rather than what
- classes and metas -are-.
-
-
- Creating Classes
-
- Classes contain two important descriptions -- attribute descriptions
- and method descriptions. Attribute descriptions are very
- straightforward. Method descriptions can become very complicated.
-
- Classes are created by specifying the new class name, the superclass,
- the new methods, and the new attributes like so:
-
- CreateSubClass(NULL, ROOT_CLASS, META_CLASS,
- MY_CLASS_NAME,
- NULL,
- myNewAttrs,
- myNewMethods,
- METHOD_END);
-
- AttributeTags
-
- The attribute description is a NULL-terminated array of ATTRIBUTE_TAGs.
- Each element contains the following information:
- typedef struct AttributeTag {
- char *attag_name;
- ULONG attag_size;
- void *attag_default;
- } ATTRIBUTE_TAG;
-
- The attag_name is the name of the attribute. When you have an instance
- of your created class you can access this attribute inside of the
- instance by calling the FindAttribute() function passing in the
- attag_name of the attribute you want access to.
-
- The attag_size is the size of the particular attribute you want
- created.
-
- The attag_default is a pointer to a structure containing the
- default values of the attribute. If this is not specified, the
- default value for the entire attribute-structure will be zeroes.
-
- Consider the following example. Say you wanted to create an object
- that described a car. You want to keep certain information
- about this car inside of an attribute.
-
- First thing is to create a C structure:
- struct CarInfo {
- ULONG ci_wheelSize;
- ULONG ci_steeringSide;
- .
- .
- .
- };
- In this example, the ci_wheelSize refers to the size of the wheels
- on the car and the ci_steeringSide refers to the side of the
- car that the steering wheel is on, and so forth.
-
- The next thing to do is to pick a name for the attribute. Please
- note that the name is case-sensitive!:
-
- #define ATTR_MY_CAR_INFO "My information about this car"
-
- We can now create the ATTRIBUTE_TAG structure:
-
- ATTRIBUTE_TAG myNewAttrs[] = {
- {
- ATTR_MY_CAR_INFO,
- sizeof(struct CarInfo),
- NULL
- },
- TAG_END
- };
-
- If we had wanted to we could have created default values for the
- attributes so that when the class' instances are created, they
- automatically have some non-zero values in them:
-
- struct CarInfo defaultCarInfo = {14, LEFT, ...};
-
- In which case the ATTRIBUTE_TAG structure becomes:
-
- ATTRIBUTE_TAG myNewAttrs[] = {
- {
- ATTR_MY_CAR_INFO,
- sizeof(struct CarInfo),
- &defaultCarInfo
- },
- TAG_END
- };
-
-
- The creation of the class would necessarily follow this. Note that
- this code makes the class a resource that will be freed when the
- process defining the resource exits:
-
- AddAutoResource(NULL, CreateSubClass(NULL,
- ROOT_CLASS,
- META_CLASS,
- CAR_CLASS,
- NULL,
- myNewAttrs,
- METHOD_END),
- CAR_CLASS);
-
- Once we had this class, we can create an instance of the class:
-
- automobile = CreateInstance(NULL, CAR_CLASS, META_CLASS);
-
- So, now that an actual car instance exists, you can access the car's
- instance variables (the attributes of the instance), with the
- following code:
-
- {
- struct CarInfo *ci;
-
- ci = FindAttribute(automobile, ATTR_MY_CAR_INFO);
-
- .
- .
- .
-
- }
-
- Once you have a pointer to the object's instance variables, you can
- manipulate the structure, change variables, etc. However, this
- pointer will only remain valid as long as the object it is assoicated
- with remains valid. In other words, once you
- "DropObject(automobile)", you may not use the "ci" variable again.
- After all, the object may have been destroyed!
-
- In addition, if this structure is to be shared across many processes,
- you will want to protect access via the semaphore system (described
- above). Fo all of my code I use the "ci" pointer to semaphore the
- entire structure. You may find a more suitable way of doing the
- same thing, if you wish. At anyrate, your code might look like:
-
- {
- struct CarInfo *ci;
-
- ci = FindAttribute(automobile, ATTR_MY_CAR_INFO);
- PSem(ci, SSEM_READ);
-
- .
- . // Various reads on the structure taking place.
- .
-
- VSem(ci);
- }
-
-
-
- The next step involves showing off a shortcut in SHADOW. SHADOW's
- attributes, as defined by both META_CLASS and META_CLUSTER, are
- created in the order that they are specified. This means that if
- you create two attributes, one after the other, a pointer to
- the first structure will easily lead you to a pointer to the second.
-
-
- struct CarInfoExternal {
- ULONG cie_wheelSize;
- ULONG cie_wheelBase;
- .
- .
- .
- };
- #define ATTR_CAR_INFO_EXT "Information about the car's externals"
-
- struct CarInfoInternal {
- ULONG cii_headClearance;
- ULONG cii_cubicCapacity;
- .
- .
- .
- };
- #define ATTR_CAR_INFO_INT "Information about the car's internals"
-
- ATTRIBUTE_TAG myNewAttrs[] = {
- {
- ATTR_CAR_INFO_EXT,
- sizeof(struct CarInfoExternal),
- NULL
- },
- {
- ATTR_CAR_INFO_INT,
- sizeof(struct CarInfoInternal,
- NULL
- },
- TAG_END
- };
-
- .
- .
- .
-
- AddAutoResource(NULL, CreateSubClass(NULL,
- ROOT_CLASS,
- META_CLASS,
- CAR_CLASS,
- NULL,
- myNewAttrs,
- METHOD_END),
- CAR_CLASS);
-
- automobile = CreateInstance(NULL, CAR_CLASS, META_CLASS);
-
- Assuming the above, the following:
-
- {
- struct CarInfoExt *ce;
-
- ce = FindAttribute(automobile, ATTR_MY_CAR_INFO);
-
- .
- .
- .
-
- }
-
- is identical to this:
- {
- struct CarInfo *ci;
-
- ci = FindAttribute(automobile, ATTR_MY_CAR_INFO);
-
- .
- .
- .
-
- }
-
- where "struct CarInfo" is declared as follows:
- struct CarInfo {
- struct CorInfoExt ci_cie;
- struct CarInfoInt ci_cii;
- }
-
- As is apparent from this, a pointer to the first structure may very
- well be useful to getting at other data. The advantages of having
- separate attributes in the first place is to save space when using
- explicitly declared default attributes, or if you wish to offer
- the ability for subclasses to alter the default values for some
- subset of your attributes without affecting some other subsets
- of your attributes.
-
-
- The above is an example of a time optimization. Instead of looking
- up both attributes separately, only one was looked up, and the
- second attribute's address was able to be hard-coded.
-
- A similar improvement is possible when looking up attributes
- frequently within methods. You can lock a global pointer to a class
- and then lookup the "struct Attribute" which contains the necessary
- information to later find the attribute locations within any instance
- of that class. Note that you will have to keep that global
- pointer "locked" in memory for the entire time that the Attribute
- structure is being used.
-
- DropObject(SetObject(&globalClass, FindShadowClass(CAR_CLASS)));
- /*
- * Note that FindShadowClass() returns a used class here.
- *
- * We use SetObject() to avoid race-conditions if
- * more than one task tries to set the variable at
- * the same time.
- */
-
- struct Attribute *globalAttr = FindAttrDefn(globalClass,
- ATTR_INFO);
-
- /*
- *
- * We should not DropObject(globalClass) until we no longer
- * need the attribute.
- */
- .
- .
- .
-
- Now, say you have some instance 'object', and it is the correct
- class (object->cob_class = globalClass or somes subclass of
- globalClass). Now you may merely add the value in the attr_offset
- field within the attribute structure to the instance address and
- arrive at the attribute within the object:
-
- .
- .
- .
- {
- struct Info *info = (void *)(((ULONG)object) +
- attr->attr_offset);
- .
- .
- .
- }
- .
- .
- .
-
-
- This will occur far faster than the FindAttribute() call. The two
- disadvantages are the initial startup costs and shutdown costs, as
- well as the necessity to keep the class "locked" in memory for the
- entire time you have a globalAttr pointer.
-
- .
- .
- .
- /*
- * CLEANUP!
- *
- * As we no longer need the globalAttr, we clear the globalClass
- */
- DropObject(SetObject(&globalObject, NULL));
-
-
- The only remaining thing to note about attributes is this: once the
- class is created, the AttributeTag structures -and- the default
- attribute structures are no longer needed by the system. The
- system copies everything into memory when the class is created, so it
- is very possible to have the AttributeTags and the default values
- reside in a text file and load the text file at runtime, and then
- purge the text file.
-
- This means that changing the default attribute structure after the
- class is created will NOT change the creation of future objects.
- You can change these default attributes pretty easily, however. Here
- is some example code. It takes a longword value within the default
- object and updates it according to a hex value:
-
- {
- ULONG *ptr = GetDefaultAttribute(myClass, ATTR_WEBPAUSE, 0);
-
- /*
- * ptr may or may not exist! If ptr did not exist, then
- * the attribute did not originally have a default
- * attribute and the machine ran out of memory trying to
- * allocate one, or the attribute itself did not
- * exist within the passed class.
- */
- if (ptr)
- /*
- * Convert the hex-string in args[0] and save the number
- * into the longword pointer.
- */
- stch_l(args[0], ptr);
- }
-
- GetDefaultAttribute() is defined as follows:
-
- /*
- * Looks for a default attribute object for the name'd
- * definition.
- *
- * Returns either NULL (failure) or the default object + offset.
- *
- * Will create a default object if it doesn't exist.
- * Will create a NEW default object if the superclass has the
- * same default object. Note, subclasses -share- the same
- * default attribute object with their superclasses
- * [or, rather, vice-versa].
- *
- * Watched attributes do not -have- default values.
- */
- void *GetDefaultAttribute(CLASS class, char *name, ULONG offset)
- {
- struct Attribute *attr = FindAttrDefn(class, name);
- struct Attribute *temp;
- struct ClasslessObject *obj;
-
- if (!attr || (attr->attr_size & FLAG_ATTR_WATCHED))
- return NULL;
-
- temp = FindAttrDefn(class->ccl_superClass, name);
- if (!(obj = attr->attr_value) ||
- (temp && obj == temp->attr_value))
- {
- if (obj)
- {
- /*
- * Create a new object for the subclass and copy
- * in the values from the superclass' default
- * object.
- */
- if (!(obj = CreateObject(obj + 1, attr->attr_size)))
- return NULL;
- } else
- {
- /*
- * If we have no default object, then the default
- * values are all zero.
- */
- if (!(obj = AllocMem(sizeof(struct ClasslessObject) +
- attr->attr_size,
- MEMF_PUBLIC | MEMF_CLEAR)))
- {
- return NULL;
- }
- /*
- * Setup the new object with a size and usecount
- */
- obj->clb_size = sizeof(struct ClasslessObject) +
- attr->attr_size;
- obj->clb_useCount = 1;
-
- }
- /*
- * Save the new default object into the class --
- * drop any old default objects there, and make sure
- * the operation is single-threaded.
- */
- DropObject(SetObject(&attr->attr_value, obj));
- }
-
- /*
- * Returns a pointer into the default object so that you
- * cn directly dork with the values.
- */
- return (void *)(((ULONG)(obj + 1)) + offset);
- }
-
-
- MethodTags
-
- As mentioned above, attribute creation is fairly straightforward --
- when compared to method creation. And that's because creating a
- method involves a lot more than just creating a couple of structures.
- It involves considering the types of methods you have available --
- synchronous, asynchronous, call-by-port, call-by-process. It also
- involves creating the actual functions and function argument tags,
- and the process classes (and instances) that you might need to run the
- method inside of.
-
- This presents the creation of several types of methods from the
- function-level up. However, you should realize that the process is
- not always so straightforward.
-
- A method's function always has at least the following four arguments:
- struct IPCMessage *msg;
- OBJECT object;
- META class;
- char *MethodID;
-
- The <msg> parameter is used by the asynchronous method sends. It
- is provided for handling some strange resource-tracking cases that
- are discussed below. This parameter is often NULL.
-
- The <object> parameter is the object on which the selector is being
- invoked. This parameter is never NULL.
-
- The <class> parameter is a pointer to the class of the object that
- actually handles the given MethodID. Remember, every object is an
- instance of some class. Classes live in a class-hierarchy from which
- they inherit other class' methods. Therefore, when a particular
- method is invoked, the method itself might be handled by the object's
- class, or some superclass of the object's class. This parameter
- distinguishes -which- class handles the passed selector....
-
- The <MethodID> parameter is a pointer to a system-string which
- corresponds to the selector for this particular method. It is a
- useful way to allow multiply different methods to converge on one
- of your functions, and then diverge when the method is sent off
- to the superclass for further handling.
-
- Obviously, typing all of this information into each function would
- be tedious. The includes defines the macro "METHOD_ARGS", which
- contains these argument definitions. The ability of your compiler to
- handle this shortcut may differ from mine....
-
- Here is a sample method-function definition:
-
- void ProcTestMethod(METHOD_ARGS)
- {
- BPTR oldoutput;
-
- oldoutput = SelectOutput(Open("CONSOLE:", MODE_OLDFILE));
-
- VPrintf("Called method: <%s> ", (ULONG *)&MethodID);
- VPrintf("in process <%s>\n", (ULONG *)&FindTask(NULL)->
- tc_Node.ln_Name);
- VPrintf("\tin Class <%s> ", (ULONG *)&(CurrentProcess()->
- cob_class->meta_name));
- VPrintf(((msg)?"Asynchronously\n":"Synchronously\n"), NULL);
-
- oldoutput = SelectOutput(oldoutput);
- Close(oldoutput);
-
- CallSuper();
- }
-
- This is a relatively simple method-function that the included perftest
- program uses to verify nearly all of the method-invoke possiblities
- (by function, synchronous msg, asynchronous msg, etc).
-
-
- Once you have this function written, you need to create an
- ARGUMENT_TAG structure which describes the addition arguments that
- the method tags (above the four described above). Because this
- method does not take any extra arguments, we will postpone talking
- about ARGUMENT_TAGs until we have a more appropriate example.
-
- Now we have to create a name for the selector of this method:
-
- #define METH_MY_NEW_PROC_TESTER "A string of your own choosing"
-
- The next thing to do is to add the method into your class' METHOD_TAG
- list. The METHOD_TAG list has eight different variables. We take
- each in turn.
-
- The first field in the METHOD_TAG structure is the mtag_MethodID
- field. This field is very straightforward -- it should always
- carry a non-NULL field that is a pointer to the string which is
- to represent the method's selector.
-
- The next two fields are named: mtag_procObject and mtag_defnObject.
- Although they have similar names, their meanings are very different.
- The mtag_defnObject is used by the system to ensure that the program
- (or some other loaded code, like a library) which contains the
- definition of the function and the ARGUMENT_TAG in its seglist,
- does NOT disappear until all reference to the function by any
- methods in any classes are eliminated.
-
-
- mtag_defnObject:
-
- Here's the basic problem. Program A has loaded a method, M. Program
- A now defines a public class C which has the method M.
- Program B now comes along, creates an instance I of class C. Program
- A quits. Program B tries to invoke method M of instance I, which used
- to reside in Program A, but has been since eliminated. This is
- clearly not good. The same thing might happen for a library base
- (although the OpenCnt of the library base should be enough to
- catch all legal errors in this respect).
-
- What we do in the instance of a library is to define a global,
- classless object, and use the pointer to this global object as the
- defn_object:
- struct ClasslessObject
- globalLibraryObject = {0, 0, 0};
-
- In the EXPUNGE code, you'll want to verify that the
- globalLibraryObject.cob_useCount is zero. If it is a non-zero
- integer, then someone still has a pointer to an object that has
- a class (or some superclass thereof) that points to a method in
- your library. Thus, your method may still be called, and you
- should refuse to expunge your library.
-
- This is a distinct advantage over BOOPSI where class libraries are
- difficult to manage because of the various resource tracking issues.
-
-
- However, creating methods inside of libraries is not likely to be
- the most frequent thing you do. Far more frequently you will be
- creating classes inside of programs. And for this, you will need to
- retrieve the CurrentProcess() -- a pointer to your process' object
- that was created during the InitOOProgram() call (the FIRST function
- you should always call after your OpenLibrary("shadow.library", 5) is
- InitOOProgram()!!! -- see the Process Class discussion below).
-
- /*
- * CurrentProcess() is a macro defined in <shadow/shadowProto.h>
- *
- * This object is not resource tracked, so you need not
- * DropObject() this processObject after you're done using it.
- */
- processObject = CurrentProcess();
-
- Once you have this pointer, you modify each of your MethodTags to
- have the mtag_defnObject point to the CurrentProcess() object.
- This is tedious, and the funcion SetupMethodTags() is provided to
- help you in this respect. We will talk more about SetupMethodTags
- when we begin the discussion of mtag_procObject.
-
- It stands to reason, as well, that you need to be in the process
- that the program runs in! In otherwords, FindTask(NULL) had
- better be your program's task, or you are going to end up with the
- wrong process!
-
- At anyrate, once your class is created, your program cannot exit
- before the methods are no longer callable via this class because the
- last call your program should make before the CloseLibrary(ShadowBase)
- is the RemoveCurrentProgram() call, which will not return until all
- references to your CurrentProcess() go away. Again, you have a safe
- way in which to define public classes and ensure no one has a pointer
- to your internal methods when your program ends up quitting and
- returning its resources to the system.
-
-
- mtag_procObject:
-
- The mtag_procObject is used to help determine what process the
- object's method needs to be run in. SHADOW is very flexible in its
- ability to run methods in other processes. You can ask for something
- as simple as a function call, or something much more complicated,
- like "invoke a method by creating a process of the given named class;
- then, replace the named class with a pointer to the actual class
- pointer so that the next invocation no longer has to look the class
- up in the class list."
-
- While the mtag_defnObject is always a SHADOW object, the
- mtag_procObject can be any number of things. Some of the mtag_flags
- are used to decide what the mtag_procObject is pointing to.
-
- A valid mtag_procObject is one of five values:
- a) it is a pointer to a process object.
- b) it is a pointer to an IPCPort.
- c) it is a pointer to a process class
- d) it is a pointer to a struct MethInvokeSpec <shadow/method.h>
- e) it is NULL
-
-
- mtag_flags:
-
- There are five flags in the mtag_flags which control the behaviour of
- these various possibilities:
-
- METH_FLAG_OBJECT - The mtag_procObject is interpreted as the
- destination process object.
- METH_FLAG_PORT - The mtag_procObject is interpreted as an
- IPCPort to which method-messages are sent.
- METH_FLAG_CLASS - The mtag_procObject is interprted as a
- class of process objects. This class is
- instantiated, and the resulting process
- object is used as the destination for
- that particular method invocation.
-
- METH_FLAG_OBJECT_AS_DEST
- - The object the method is being invoked on is
- itself used as the destination process
- object. This is particularly useful for
- process methods which must (for reasons of
- port allocation or other signal-bit
- allocation, etc.) be run in the process
- with which the process-object corresponds.
-
- METH_FLAG_SPEC - The mtag_procObject is interpreted as a
- pointer to a struct MethInvokeSpec
- [see: <shadow/method.h>], the complete
- meaning of which depends on the above four
- flags in the following manner:
-
- METH_FLAG_SPEC | METH_FLAG_OBJECT
- mis_instanceName is the name of the process object to use
- as the destination object.
- mis_className is the class name of the class of the
- above process object.
- mis_metaName is the meta's name of the meta of the
- above class. Usually this is
- META_CLASS.
-
- METH_FLAG_SPEC | METH_FLAG_PORT
- mis_instanceName is the name of the IPCPort to use as the
- destination port. This port will NOT
- be created, but must exist when the
- method is called.
- mis_className is NULL
- mis_metaName is NULL
-
- METH_FLAG_SPEC | METH_FLAG_CLASS
- mis_instanceName is NULL
- mis_className is the class name of the process to
- create.
- mis_metName is the meta's name of the meta of the
- above class. Usually this is META_CLASS.
-
- METH_FLAG_SPEC | METH_FLAG_OBJECT_AS_DEST
- the METH_FLAG_SPEC is meaningless in this case as the
- METH_FLAG_OBJECT_AS_DEST never uses the mtag_procObject
- field.
-
-
- When the process/port is looked-up/created,
- the process/port may replace the
- MethInvokeSpec structure, eliminating the
- need to lookup the object/class/port again.
- To do this, set the SPEC_SAVE_BINDING flag
- in the mis_flags field of the MethInvokeSpec
- structure.
-
-
- A NULL mtag_procObject is treated differently depending on the
- mtag_flags and the mtag_threadstat. As the mtag_threadstat hasn't
- been discussed yet, though, it makes sense to do that first.
-
-
- mtag_threadstat:
-
- Where the mtag_flags field describes how the mtag_procObject is
- interpreted during a method call, the mtag_threadstat determines how
- the method is actually invoked. The following flags are defined in
- <shadow/message.h>:
-
- INVOKE_CALL - Calls method as function.
-
- INVOKE_SYNC - The method is invoked as a synchronous message
- to the method's mtag_procObject, unless the
- calling process belongs to a subclass
- of the destination's process' class,
- in which case the method is called as a
- function.
-
- INVOKE_ASYNC - The method is invoked as an asynchronous
- message to the method's mtag_procObject,
- unless the calling process belongs to a
- subclass of the destination's process'
- class, in which case the method is called
- as a function.
-
- INVOKE_FORCE - Used in onjunction with above two flags. See
- next two flags.
-
- INVOKE_FORCE_SYNC - The method is sent as a synchronous message
- unless the calling process is exactly
- equivalent to the destination process, in
- which case the method is called as a funcion.
-
- INVOKE_FORCE_ASYNC- The method is ALWAYS sent as an asynchronous
- message. Irregardless of calling/destination
- process.
-
- INVOKE_IGNORE_PROCESS
- - It is possible for programers to call DSM()
- and its associated helper functions
- (eg. DoShadowInProcess()) to override the
- process that the method is handled in.
- Setting this flag in the mtag_threadstat
- prevents the mtag_procObject from being
- overridden with the INVOKE_WITH_PROCESS
- flag that can be sent to DSM(). See the
- Autodoc for DSM() for more information.
-
-
- As you can see, there are a variety of ways in which to call a method.
- You can call a method asynchronously to a named IPCPort (mtag_flags
- would be METH_FLAG_PORT | METH_FLAG_SPEC, mtag_threadstat would be
- INVOKE_FORCE_ASYNC, and mtag_procObject would be a pointer to a
- MethInvokeSpec structure) or invoke a method as a simple function
- call (mtag_flags would be METH_FLAG_OBJECT, mtag_threadstat would be
- INVOKE_CALL, mtag_procObject would be NULL). Many of the callable
- attributes are overridable at method invoke-time, so you can
- determine how the method is invoked both in the method definition
- (which is being discssed here) and method invocation (which will be
- described briefly here, and is described more fully in the DSM()
- autodoc).
-
- When mtag_procObject is NULL, there are several possible reactions by
- SHADOW, depending (as mentioned above) on the mtag_flags and
- mtag_threadstat settings:
-
- INVOKE_CALL - NULL mtag_procObject is ignored, call
- proceeds as usual.
-
- mtag_flags = METH_FLAG_OBJECT:
-
- INVOKE_[FORCE]_SYNC - Method is only successfully called if the
- calling task has no associated SHADOW
- object. Otherwise DSM() or its helper
- functions (eg. DoShadow()) returns NULL.
- If the calling task has no associated
- SHADOW object, the method is invoked as
- a function call.
-
- INVOKE_[FORCE]_ASYNC- Similar logic to the INVOKE_SYNC logic
- above except that INVOKE_FORCE_ASYNC where
- the mtag_procObject is NULL will force an
- asynchronous method-invoke message to be
- sent to the calling task's SHADOW-method
- port, stored in the sp_port field of the
- struct ShadowProcess structure in the
- ATTR_SHADOWPROCESS attribute of the
- process' object. If there is no current
- task, the message should be called as a
- function.
-
-
- mtag_flags = METH_FLAG_PORT
- -or-
- mtag_flags = METH_FLAG_CLASS
-
- A NULL port/class (mtag_procObject), except when mtag_threadstat
- is INVOKE_CALL, will cause the method invocation to fail. DSM(),
- or its helpers (eg. DoShadow()), will return NULL.
-
-
- mtag_flags = METH_FLAG_OBJECT_AS_DEST
-
- A NULL mtag_procObject is meaningless as the object argument of
- the DoShadow() call (or the first pointer in the arguments
- array to DSM()) is used as the process object to which to send
- the method.
-
-
- mtag_flags | METH_FLAG_SPEC
-
- Not specifying a pointer to a MethInvokeSpec structure in the
- mtag_procObject when the flag METH_FLAG_SPEC is set in the
- mtag_flags field is a programming error. Your class will not
- be created under these conditions. mtag_procObject MUST be
- non-NULL, and had BETTER point to a MethInvokeSpec structure.
- This MethInvokeSpec structure is copied when the class is
- created, so you need not have your MethInvokeSpec structures
- lying around after your classes are created.
-
- mtag_priority
- This field is only used by the PATCHER_CLASS. It is ignored
- by the class, though you set it to zero for future
- compatibility.
-
- mtag_method
- This should contain a pointer to the function which implements
- the method.
-
- mtag_arguments
- This should point to an ARGUMENT_TAG array that describes the
- arguments the method's function takes (this allows asynchronous
- methods to have their arguments correctly resource-tracked),
- and describes the return value of the function. If the function
- does not return anything, and there are no arguments above and
- beyond the METHOD_ARGS default arguments of all methods, then
- this field may be NULL. The pointer to this supplied
- ARGUMENT_TAG is -not- copied, although the contents of the
- ARGUMENT_TAG MUST remain constant. The ARGUMENT_TAG should
- be considered a part of the method's function, rather than as
- an extension to the data that describes the method (METHOD_TAG).
- That data (the METHOD_TAG) is copied and altered appropriately
- when the class is created.
-
-
- Given this information, here is an example METHOD_TAG which enables
- the associated class to invoke the METH_MY_NEW_PROC_TESTER method
- and have this translated into a function call to the ProcTestMethod()
- function which was described several pages ago:
-
- METHOD_TAG myNewMethods[] = {
- .
- .
- .
- <other methods for this class>
- .
- .
- .
- {
- METH_MY_NEW_PROC_TESTER,
- NULL, NULL,
- INVOKE_CALL,
- METH_FLAG_OBJECT, 0,
- (METHODFUNCTYPE)ProcTestMethod,
- NULL
- },
- TAG_END
- };
-
- Notice that this method tag does not specify an mtag_procObject nor
- an mtag_defnObject. For this particular METHOD_TAG item, the
- mtag_defnObject needs to be set to the CurrentProcess() and the
- mtag_procObject can remain NULL. However, it is quite possible that
- other METHOD_TAG items need to have their mtag_procObject be
- something other than NULL, and it is perfectly fine to specify
- an mtag_procObject as any process object you wish when the
- mtag_threadstat is INVOKE_CALL and the METH_FLAG_OBJECT is set.
-
- As the CurrentProcess() is only available at runtime, you can either
- iterate through the METHOD_TAG array and setup the defnObject and
- the procObject as you wish, or you can use the supplied
- SetupMethodTags() routine.
-
- SetupMethodTags takes three arguments -- the METHOD_TAG array, the
- procObject to be used in each element of the array, and the defnObject
- to be used in each element of the array. If either of these last two
- arguments are specified as "-1", then the results of CurrentProcess()
- are used. If the item already has a defnObject or procObject
- specified (non-NULL), then that field remains untouched. This is
- especially useful when using the MethInvokeSpec structure as the
- mtag_procObject -- this is a static structure which can be set at
- compile-time....
-
- Please note! When setting the elements of this array, SetupMethodTags
- does not UseObject() any of the objects. Therefore, be careful if
- you ever define a new type of MetaClass which allows for classes to
- be defined asynchronously to the caller, as the mtag_procObjects and
- the mtag_defObjects will not be correctly resource-tracked.... This
- is not a problem for the default CreateSubClass() call which 99% of
- you will be using.
-
- The corresponding SetupMethodTags() for this METHOD_TAG structure
- looks as follows:
-
- /*
- * Set all procObjects and defnObjects to CurrentProcess();
- */
- SetupMethodTags(myNewMethods, (void *)-1, (void *)-1);
-
- The creation of the class would then follow this. Note that this
- code makes the class a resource that will be freed when the
- process defining the resource exits -- also, we use the "myNewAttrs"
- ATTRIBUTE_TAG which is defined several pages back:
-
- AddAutoResource(NULL, CreateSubClass(NULL,
- PROCESS_CLASS,
- META_CLASS,
- MY_PROC_CLASS,
- NULL,
- myNewAttrs,
- myNewMethods,
- METHOD_END),
- MY_PROC_CLASS);
-
-
- We now possess a new MY_PROC_CLASS with some attributes and some methods.
- Of course, to call a method, we first need an instance!
-
- process = CreateInstance(NULL, MY_CAR_CLASS, META_CLASS);
-
- And now to call the new method:
-
- DoShadow(process, NULL, METH_MY_NEW_PROC_TESTER, METHOD_END);
-
- .
- .
- .
- /*
- * Don't forget your cleanup!
- */
- RemoveObject(process);
-
-
- The DoShadow() call is very straightforward -- first the object, then
- the class at which to begin looking for the method (NULL signifies
- object->cob_class, which is the way you'll call 80-90% of all your
- methods), then the selector string, then any arguments that are
- expected by the method, and then a METHOD_END terminator.
-
- A few things need to be pointed out here. First, you need not
- specify all your arguments before the METHOD_END. Arguments
- which are not specified are set to zero by the method calling code.
- This allows you to create new versions of your software, adding new
- arguments to old methods, without having to worry about anybody
- else's additions to your classes! Provided, of course, that NULLs
- default to the old version's behaviour....
-
- Second, not specifying the METHOD_END terminator is a bug in your
- code -- don't do it, even if it does work....
-
- Third, it is VERY IMPORTANT that no argument that you pass into the
- method has the same value as METHOD_END -- 0x80000001 -- or,
- alternatively, that no two adjacent WORD arguments, combined, form a
- 0x80000001. This value was picked so as to be a very high negative
- number, an odd number (impossible address), and just plain unlikely
- to cause problems. You will have to become clever if this is not the
- case for you.... Someday, a language might hide this detail from you,
- but you'll have to live with it for now. If this is just not
- satisfactory, then let me suggest an alternative. For all those
- items that require resource tracking -- strings, objects, structure
- pointers, pass those as separate arguments, and for all flags and
- numbers, etc., pass those arguments in a single method-argument as a
- TagArray structure pointer. There is a 'TAGL' type of argument that
- will allow you to do just such a thing....
-
- Fourth, the CreateSubClass() and the CreateInstance() both have a
- METHOD_END terminator, and it now becomes clearer why -- they
- both cleverly use the passed stack arguments as arguments into a
- method call which creates either an instance of a class or a
- class itself....
-
-
- There is no reason why more than one method can call the same
- function. The following is perfectly reasonable:
-
- #define METH_MY_NEW_PROC_TESTER_1 \
- "A string of your own choosing 1"
- #define METH_MY_NEW_PROC_TESTER_2 \
- "A string of your own choosing 2"
- METHOD_TAG myNewMethods[] = {
- {
- METH_MY_NEW_PROC_TESTER_1,
- NULL, NULL,
- INVOKE_FORCE_ASYNC,
- METH_FLAG_OBJECT, 0,
- (METHODFUNCTYPE)ProcTestMethod,
- NULL
- },
- {
- METH_MY_NEW_PROC_TESTER_2,
- NULL, NULL,
- INVOKE_SYNC,
- METH_FLAG_PORT, 0,
- (METHODFUNCTYPE)ProcTestMethod,
- NULL
- },
- TAG_END
- };
-
- .
- .
- .
-
- extern struct IPCPort *somePortSomeplace;
-
- /*
- * Set all procObjects and defnObjects to CurrentProcess();
- */
- SetupMethodTags(myNewMethods, (void *)-1, (void *)-1);
- /*
- * oops, but reset the port.
- *
- * We could have the line below and the line above switch
- * places as well!
- */
- myNewMethods[1].mtag_procObject = somePortSomeplace;
-
- /*
- * Create the class.
- */
- AddAutoResource(NULL, CreateSubClass(NULL,
- PROCESS_CLASS,
- META_CLASS,
- MY_PROC_CLASS,
- NULL,
- myNewAttrs,
- myNewMethods,
- METHOD_END),
- MY_PROC_CLASS);
-
- AddAutoResource(NULL, process = UseObject(
- CreateInstance(NULL,
- MY_CAR_CLASS,
- META_CLASS)),
- NULL);
-
-
- /*
- * Send off some methods....
- *
- * The first is called asynchronously.
- * The second is called synchronously and is handled by the
- * "somePortSomeplace" IPCPort. The same function
- * (ProcTestMethod()) should be called regardless of
- * this, however.
- */
- DoShadow(process, NULL, METH_MY_NEW_PROC_TESTER_1, METHOD_END);
- DoShadow(process, NULL, METH_MY_NEW_PROC_TESTER_2, METHOD_END);
-
- .
- .
- .
- /*
- * Hey, don't forget the cleanup!
- */
- DropObject(process); /*
- * Matches my UseObject()
- * above
- */
-
- /*
- * Oh, and I haven't shown shutdown recently....
- */
- RemoveCurrentProgram(NULL); /* removes the process and the
- process' class as they were added
- as autoresources.
- */
- CloseLibrary(ShadowBase);
- .
- .
- .
-
-
- ArgumentTags
-
- The remaining mystical portion to methods (if you've been following
- everything else I've written) are the ARGUMENT_TAGs.
-
- ARGUMENT_TAGs describe the arguments that a method receives, above
- and beyond the nominal METHOD_ARGS, which ALL methods receive.
- While it is only the number and size of the arguments which
- interests the INVOKE_CALL and INVOKE_SYNC types of calls, the type
- of the argument is very important when invoking a method
- ASYNChronously -- this is how the arguments get properly resource-
- tracked across asynchronous tasks! In addition, if resources are
- returned from an asynchronous invocation, they need to be thrown
- away -- the ARGUMENT_TAG provides for this behaviour as well.
-
- An ARGUMENT_TAG, therefore, comes in two parts -- the first part
- describes each of the arguments -- the type, the size that is
- required for them on the stack (yes, you can pass a structure
- to a method by-value), and any additional information that
- might be needed (the size of a pointer, the size of each element
- in an array, etc.). The second part serves as both the ARGUMENT_TAG
- terminator (the TAG_END) and contains the information about the
- returned resource (the type and the additional information fields,
- the size on the stack is not applicable as DSM() only returns
- a single long-word value in d0).
-
- The following is taken from the AutoDoc for SetMethodArgs(), a
- function which you will not need to use in all likelihood, but,
- nevertheless, the place where information about ARGUMENT_TAGs
- was previously kept. Note that the definition for the AttributeTag
- structure resides in <shadow/misc.h>:
-
- If the method-function returns a variable that needs to be kept
- track of, the last ArgumentTag structure (whose at_tag should be
- zero) in the array should contain information for async. method
- sends to safely deallocate or otherwise resource track the
- returned value.
-
- Valid returns are:
- SHADOW_RETURN_BLANK 0
- SHADOW_RETURN_OBJECT 1
- SHADOW_RETURN_STRING 2
- SHADOW_RETURN_PORT 3
- SHADOW_RETURN_TAGL 4
- SHADOW_RETURN_PTR 5
- SHADOW_RETURN_MRFO 6
-
- These values should be stored in the at_size field of the last
- ArgumentTag array item..
-
- For SHADOW_RETURN_PTR, the size of the pointer should be stored
- in the at_flags field of the same item, for SHADOW_RETURN_TAGL,
- the size of each array item should be stored in the at_flags field
- of this last item, and for SHADOW_RETURN_MRFO, the offset of the
- returned data from the object in question should be placed in the
- at_flags field
-
- Normally, that is, in every case except the last item, the system
- supports a number of at_tag values:
- 'MRFO':
- used for object values which add at_flags to
- the passed object pointer.
- at_size should be four
- at_flags -- the offset of the passed data from the object ptr.
-
- Note that MRFO exists for system use, however you are free to
- use the item as well -- but it's of little to no use, just
- pass the object instead!
-
- 'JOBJ':
- Used for an object.
- at_size should be four
- at_flags should be one of:
- SHADOW_OBJECT
- SHADOW_CLASSLESSOBJECT
- SHADOW_META
- SHADOW_CLASS,
- SHADOW_CLUSTER
- SHADOW_COMPOSITE
- though this is more for peace of mind than necessity.
-
- 'JMSG':
- Used for a message creted by MessageMaker()
- at_size should be four
- at_flags should be zero
-
- 'JSTR':
- Used for a system string.
- at_size should be four
- at_flags should be zero
-
- 'PORT':
- Used for a ppipc port.
- at_size should be four
- at_flags should be zero
-
- 'TAGL':
- Used for a NULL terminated array.
- at_size should be four.
- at_flags should be the size of each array item
-
- 'RTRN':
- reserved by the system, don't use.
- 'APTR'
- a pointer to some memory.
- at_size should be four.
- at_flags should be zero if you don't want to copy the data to
- another block when sending async., or should be the size of
- the data pointed to if you do.
-
- Create you own 4 character constant:
- at_size should be an even number -- you can pass arbitrarily
- large structures on the stack, this way.
- at_flags ignored.
-
-
- If we examine closely, a few limitations crop up. For one thing,
- there doesn't seem to be a way to add another "type" of argument that
- would require a different kind of resource-tracking. This is TRUE --
- please send me your requests!
-
- Second, you can't pass a structure pointer that point to a structure
- of more than 65535 bytes (at_flags is a UWORD value). If you need to
- pass a 64k or bigger structure, feel free to prepend the three
- long-word values which would be required for a struct
- ClasslessObject, set the classless object's size to the final size of
- the larger structure, and pass the large structure as an object.
- The structure will thus not be copied between asynchronous tasks
- (saving time and space). If you need to copy the object, use the
- original structure (without the struct ClasslessObject header) in a
- call to CreateObject() like this:
-
- newObject = CreateObject(bigStructurePointer,
- sizeof(BigStructure));
-
- This will create a new ClasslessObject structure with the values found
- in the passed bigStructurePointer copied into the returned object.
- The method call would proceed:
-
- DoShadowAsync(object, NULL, SOME_METHOD, newObject, METHOD_END);
- /*
- * No Longer need this!
- */
- DropObject(newObject);
-
- Similarly, the array item in a TAGL can not be larger than 65k. The
- array can be larger, just not each individual item....
-
-
- The following example introduces ARGUMENT_TAGs in a real-world
- application. The code is analogous to the METH_INIT code for the
- ROOT_CLASS. This method (as the browser indicates) takes a single
- parameter -- a 'JSTR'. This string is used to add the object to
- some kind of global (avl) tree structure. If the string is NULL,
- then the object's address is used instead. For the ROOT_CLASS'
- METH_INIT, the global tree ends up being the object's class'
- ATTR_INSTANCETREE tree, not a global tree as in this example:
-
- METHOD_TAG carMethods[] = {
- .
- .
- .
- {
- METH_CAR_REGISTER
- NULL, NULL,
- SHADOW_MSG_CALL,
- METHOD_FLAG_PROC, 0,
- RegisterCarMethod,
- REF_RegisterCarMethod
- },
- TAG_END
- };
-
-
- .
- .
- .
- /*
- * The MY_CAR_CLASS methods.
- *
- * METH_CAR_REGISTER:
- */
- ARGUMENT_TAG REF_RegisterCarMethod[] =
- {
- {'JSTR', 4, 0}, /* The one argument */
- {TAG_END, SHADOW_RETURN_OBJECT, 0}
- /* The return spec. */
- };
- OBJECT RegisterCarMethod(METHOD_ARGS, char *string)
- {
- /*
- * This method "swallows" the object passed in and
- * either returns or removes it. Therefore, we must
- * transfer the object out of any asynchronous messages
- * we might receive.
- *
- * The "0" is used because "object" is the "zeroth"
- * argument to this method. "class" is first, "MethodID"
- * is second, "string" is third, etc.
- */
-
- IPCARGTransfer(msg, 0);
-
- /*
- * If there is a name, add the object to the tree with that
- * name, otherwise add the object using the object's address.
- */
- if ((!string && AddWatchedTreeNode(globalTree
- object,
- (long)object)) ||
- (string && AddWatchedTreeStringNode(globalTree,
- object,
- string)))
- {
- return object;
- }
-
- /*
- * Failure case, cleanup!
- */
- RemoveObject(object);
- return NULL;
- }
-
- .
- .
- .
-
- /*
- * Creating the MY_ROOT_CLASS
- */
- SetupMethodTags(rootMethods, (void *)-1, (void *)-1);
- myRoot = CreateSubClass(NULL, ROOT_CLASS, META_CLASS,
- MY_CAR_CLASS,
- NULL,
- NULL,
- /* No new attrs */
- rootMethods,
- METHOD_END);
-
- AddAutoResource(NULL, myRoot, MY_ROOT_CLASS);
- .
- .
- .
- /*
- * No Cleanup necessary!
- * Just call RemoveCurrentProgram() and get out!
- * The MY_ROOT_CLASS wil be -terminated- automagically.
- */
- RemoveCurrentProgram(NULL);
- CloseLibrary(ShadowBase);
- .
- .
- .
-
-
- This example uses one argument specification -- a 'JSTR' -- and
- then specifies that the method returns an object. We can call
- this method using the following (assuming this code is run
- somewhere between the CreateSubClass() and the
- RemoveCurrentProgram() calls in the above code example!):
-
- /*
- * Create the object
- */
- auto = CreateInstance(NULL, MY_ROOT_CLASS, META_CLASS,
- METHOD_END);
-
- /*
- * Send off the method.
- */
- auto = DoShadow(auto, NULL, METH_CAR_REGISTER, METHOD_END);
-
- -or-
-
- auto = DoShadow(auto, NULL, METH_CAR_REGISTER, "some object",
- METHOD_END);
- /*
- * Cleanup!
- */
- RemoveObject(auto);
-
-
- This method is also safe to call asynchronously!
-
- DoShadowAsync(auto, NULL, METH_CAR_REGISTER, "some object",
- METHOD_END);
-
- /*
- * Cleanup!
- *
- * It is possible for the METH_REMOVE method to be called
- * twice in this case....
- */
- RemoveObject(auto);
-
-
- Indeed, ARGUMENT_TAGs are most heavily used for the asynchronous case.
- The DoShadowAsync() attempts to invoke an asynchronous method on the
- auto. This causes DSM() to create an IPC message with several items
- of interest -- the auto object, the string argument, and the return
- value are the only items that matter for this example.
-
- The auto object has its cob_UseCount incremented via UseObject().
- The string argument is created with the UseString() call.
- The return value is set to return an object (specifically, the
- ipc_Id is set to 'JOBJ'). The message is then sent off to the
- destination process.
-
- When the destination process receives the message, the method ends
- up being called (within ParseShadowMessage()). The method
- specifically removes the "object" (the "auto") from the message.
- This method is defined as "swallowing" the auto-object, so the
- message is updated to reflect this. The "auto" is then added to some
- globalTree. If the addition is successful, the "auto" is returned.
- Otherwise the "auto" is Remove()'d from the system.
-
- When the auto is returned, it is placed into the return slot in the
- message. ParseShadowMessage() then attempts to return the message.
- However, asynchronous messages do not have a ReplyPort, so
- ParseShadowMessage() ends up calling JunkIPCMessage() which destroys
- each item's reference and then destroys the message.
-
- In order, then, the return object is Drop()'d via DropObject(), the
- auto-object is ignored (remember the IPCARGTransfer()!), the
- string is Drop()'d via DropString(), and the message is destroyed.
-
- Note, if you hadn't specified the argument in the ARGUMENT_TAG,
- char *string would have ended up being 0x80000001 (or worse, if
- you had more arguments, those other arguments might be the return
- address of the function or stored registers, etc.). It is very
- important that your ARGUMENT_TAG matches your method's actual
- parameter specification. In addition, if your SHADOW_RETURN_*
- field is incorrect (or left unspecified), improper resource
- tracking of the return object might take place. For instance, if
- the ARGUMENT_TAG were specified as follows:
-
- ARGUMENT_TAG REF_RegisterCarMethod[] =
- {
- TAG_END
- };
-
- Then the passed string would take on the value of 0x80000001 and the
- returned object in the asynchronous case would be "lost." It would
- never be Drop()'d from the system, and thus repeated use of the
- function (asynchronous use!) would cause memory loss and
- possibly memory fragmentation.
-
- Many macros have been defined in <shadow/misc.h> which allows you
- to build ARGUMENT_TAGs more easily. The correct
- REF_RegisterCarMethod[] would look as follows:
-
- ARGUMENT_TAG REF_RegisterCarMethod[] =
- {
- ARG_STR,
- RET_OBJ
- }
-
- Which is to say, one string argument, and the return of an object.
-
- Using these macros, we define an example which uses all of the
- important types of method arguments:
-
- ARGUMENT_TAG REF_RaceCarMethod[] =
- {
- ARG_OBJ,
- ARG_STR,
- ARG_TAGL(sizeof(struct TagItem)),
- ARG_MESG,
- ARG_PORT,
- ARG_INT,
- {'stuf', sizeof(struct StockCar), 0},
- ARG_PTR(0),
- RET_NORMAL
- }
-
- /*
- * Note that the error message field is actually never
- * deleted, this should be done by the caller. Note
- * also that deleting the message even after an
- * asynchronous method invocation works because the message
- * is -copied- for the asynchronous send, and then the copy is
- * magically deleted after the method-function is done
- * executing.
- *
- * ParseShadowMessage() is usually called with the
- * SHADOW_RETURN_MSG_NEVER, but this is an example of an
- * alternative usage pattern. Please refer to the AutoDocs
- * for more information about ParseShadowMessage()'s flags.
- */
- BOOL RaceCarMethod(METHOD_ARGS,
- OBJECT registry,
- char *name,
- struct TagItem *tags,
- struct IPCMessage *error,
- struct IPCPort *port,
- long failure_probability,
- struct StockCar sc,
- void *garbage)
- {
- if (!...use all the arguments to race the car...)
- {
- if (port && error)
- {
- error->ipc_Msg.mn_ReplyPort = GlobalReplyPort;
- if (PutIPCMessage(port, error);
- {
- /*
- * A simplified version of code to retrive
- * the message back from the error handling port.
- */
- WaitPort(GlobalReplyPort);
- GetMsg(GlobalReplyPort);
- } else
- ParseShadowMessage(error,
- SHADOW_RETURN_MSG_ALWAYS);
- } else if (error)
- ParseShadowMessage(error, SHADOW_RETURN_MSG_ALWAYS);
- return FALSE;
- }
- return TRUE;
- }
-
-
- There are four specific things I want to mention with this example.
- First, the ARG_TAGL need NOT have an item size identical to the
- size of struct TagItem. Any array of items whose last item begins
- with a zero longword is considered a valid ARG_TAGL.
-
- Second, the ARG_MESG parameter is a message which holds a "preparsed"
- version of a method call. You can create a message to pass into
- this method by using the following as an example:
-
- errorMessage = PreParseShadow(car, NULL, METH_CAR_ERROR,
- "Bad Race!",
- METHOD_END);
-
- The errorMessage contains a message which can be sent to another port
- or Parse()d out at some later time. The METH_CAR_ERROR method is not
- actually called; the returned message, however, contains all the
- pertinent information to call the method if you later decide to. To
- get rid of the message, you can either call ParseShadowMessage:
-
- ParseShadowMessage(errorMessage, SHADOW_RETURN_MSG_NEVER);
-
- which will invoke the method, and then destroy the errorMessage; or
- you can call JunkIPCMessage to destroy the message:
-
- JunkIPCMessage(errorMessage);
-
- Third, the "('stuf', sizeof(struct StockCar), 0}" is an example of
- how to pass large structures on the stack to a method. To operate
- properly on 68000 based machines, the sizeof() had better be even.
- For best performance, it should be at least long-word aligned.
-
- Lastly, the ARG_PTR(0) means to pass a pointer to the function, but
- not to copy the data to another pointer if resource tracking would
- have normally required it (ie: asynchronous methods). If you wanted
- to pass the StockCar structure in by pointer instead of on the
- stack, you could conceivably use:
-
- ARG_PTR(sizeof(struct StockCar)),
-
- instead of:
-
- {'stuf', sizeof(struct StockCar), 0},
-
- and define the method as
-
- ., /* Args deleted! */
- .,
- .,
- struct StockCar *sc,
- void *garbage)
-
- instead of:
-
- ., /* Args deleted! */
- .,
- .,
- struct StockCar sc,
- void *garbage)
-
- This alternative should run faster than requiring stack copying as in
- the original example.
-
-
- Assumeing you had an object of the correct class, the original method
- invocation might look as follows:
-
- errorMsg = PreParseShadow(car, NULL, METH_CAR_ERROR,
- "Bad Race!",
- METHOD_END);
-
- tags[0].ti_Tag = CAR_FORMULA;
- tags[0].ti_Data = 2;
- tags[1].ti_Tag = TAG_END;
- DoShadow(car, NULL, METH_CAR_RACE, registration, "Road Runner",
- tags, errorMsg, NULL, 85, myStockCarStructure, NULL,
- METHOD_END);
- JunkIPCMessage(errorMsg);
-
- This would work regardless of whether the method was defined to run
- synchronously (as a function call or a synchronous message) or
- asynchronously. This independence allows you to create code that
- will run in a wider array of circumstances, and allow you to further
- tune your application by running various parts of your program in
- separate processes.
-
-
- CreateInstance:
-
- I have been using some wrapper functions in all of my method
- discussions -- CreateInstance and CreateSubClass. In reality, they
- are really method calls. CreateInstance() actually contains two
- methods as in the following pseudo-code:
-
- CreateInstance(privateClass, className, metaName, <..args..>)
- {
- OBJECT obj;
-
- if (!UseObject(privateClass))
- {
- created = TRUE;
-
- if (metaName)
- privateClass = FindInstanceOfMeta(className, metaName);
- else
- privateClass =
- FindNodeWatchedTree(&ShadowBase->sb_metaTree,
- metaName);
- }
-
- /*
- * Create the object's memory.
- */
- obj = DoShadow(privateClass, NULL, METH_CREATE, METHOD_END);
- DropObject(privateClass);
-
- /*
- * Make it a real object.
- */
- ret_val= DoShadow(obj, NULL, METH_INIT, <..args..>);
- return ret_val;
- }
-
- The first method (METH_CREATE) actually creates memory for the
- object to exist in. In addition, it sets up the class pointer for
- the object. However, it is not, at this point, a valid object. Were
- something to fail here, or in the METH_INIT, the only way to get the
- object to actually go away is to do the following:
-
- DropObject(UseObject(obj));
-
- This is in direct contrast to the normal "DropObject(obj)" that is
- usually called. Programmers of METH_INIT take note! The
- METH_DESTROY is only called when the usecount falls to zero.
- However, if it is zero to begin with (as, say, it is when it
- returns from the METH_CREATE method), you must first increment
- the usecount, and -then- decrement it.
-
- It is the METH_INIT that follows which is responsible for setting
- up the usecount to properly be non-zero. And, of course, to setup
- the object correctly.
-
-
- The CreateSubClass is a simpler wrapper that ends up calling the
- METH_SUB method of a particular class. The "privateClass" pointer
- lookup is exactly the same in the CreateSubClass() as in the
- CreateInstance(); however, it is the METH_SUB method that actually
- invokes the METH_CREATE and the METH_INIT, not CreateSubClass()....
-
- Wrap-up of Methods:
-
- There are many ways to call methods besides DoShadow(). We have seen
- at least two examples -- DoShadowAsync() and PreParseShadow(). The
- alternatives are documented in the ShadowLibFuncs.doc. In
- addition, the root SHADOW library code for all of these functions is
- DSM(), and that is documented in ShadowLibraryFuncs.doc (autodoc). I
- highly recommend using these references to learn more about the
- method invocation code. We are about to go on to the more advanced
- topics of PATCHER_CLASS, PROCESS_CLASS and DIRECTOR_CLASS which will
- introduce even more complexity into an already complex picture, this
- is a convenient break point to try some example code and see what
- happens (let me know of any bugs!). It will allow you to get a feel
- for the programming environment that SHADOW lives within and will
- make you feel more comfortable about the next three topics.
-
-
-
- PATCHER CLASS
-
- You will notice that in the above discussion about methods there were
- several METH_FLAGS_* that were never covered and left later for the
- discussion about PATCHER_CLASS. Well, the time has come.
-
- Yes, that's right, just as you thought you were beginning to
- understand everything about SHADOW's methods, some new twist pops up
- to confuse the issue. PATCHER_CLASS is a rather good example of
- this.
-
- Essentially, all methods within all SHADOW classes are patchable.
- Patching is analogous to SetFunction(), except that great pains have
- been taken to assure that the problems with SetFunction() are NOT
- shared by PATCHER_CLASS. Unfortunately, you also lose some of the
- flexibility of SetFunction().
-
- When a method is patched, the result is referred to as a
- "patch-chain." DSM(), along with all of its other duties, is
- responsible for traversing the patch-chain and calling each of the
- separate method patches in turn and returning the most-recent, valid
- return value.
-
- Each patch is created in very nearly the same manner that the base
- methods were created -- through the use of the METHOD_TAG structure.
- Note, however, that each patch will require ONLY ONE METHOD_TAG
- item -- please do not attempt to pass single METHOD_TAG *items* to
- functions like SetupMethodTags() which requires a METHOD_TAG *array*.
- This will not work, so you'll have to setup the mtag_procObject and
- mtag_defnObject fields by hand (or put all of your METHOD_TAG patches
- in one big array and call SetupMethodTags() on this array.
-
- There are two significant differences between the METHOD_TAGs that
- are used for the base methods and the METHOD_TAGs that are used for
- patches. The former ignores the mtag_priority field while the
- latter uses it to order the patch-chain. The patches in the patch
- chain are called in descending numerical order where the base
- method is always assumed to exist at priority zero.
-
- The second difference is the additional mtag_flags values which,
- while supported by the base methods, gain real power only when
- patch-chains exist:
-
- METH_FLAG_PRE_BLOCK
- METH_FLAG_POST_BLOCK
- METH_FLAG_CHECK_CONTINUE
- METH_FLAG_NO_RTRN
-
-
- METH_FLAG_PRE_BLOCK:
- If this flag is set, DSM() will stop invoking the patches within the
- patch-chain. Additionally, the patch/base-method itself will not
- get invoked. This is useful if you want to interrupt your
- patch-chain during the debugging phase, or if you want to disable
- some of the features in a demo version -- simply add the PRE_BLOCK to
- the base methods' mtag_flags, or add a PRE_BLOCK'd patch at some
- relatively high priority to the class to be affected.
-
- METH_FLAG_POST_BLOCK:
- If this flag is set, DSM() will stop invoking the patches within the
- patch-chain, but only after invoking this patch/base-method. This is
- useful for replacing a base-method during a bug-release update. Add
- a POST_BLOCK'd patch to the base-method at a priority greater than
- zero and the base-method is effectively replaced by the patch.
-
- METH_FLAG_CHECK_CONTINUE:
- If this flag is set, the patch returns an additional value in d1 which
- informs DSM() whether or not to continue the patch. This allows you
- to dynamically change whether your patch executes, or the base-method
- executes. If d1 is returned as zero from the patch, then the
- patch-chain invocation ends and the latest, valid return value is
- returned. Otherwise, the patch-chain continues as it would have.
-
- METH_FLAG_NO_RTRN:
- This informs DSM() that your patch or method does not return a valid
- value, so any previous return values that have accumulated while
- traversing the patch-chain should remain intact. Note that this is
- a significant inversion of meaning from SHADOW 4's
- METHOD_FLAG_RTRN_ME.
-
-
- Several important notes should be made at this point. First, the
- traversal through the patch chain is controlled by a semaphore on the
- patch-chain's list. This means that if you attempt to do something
- silly like have a base-method which patches itself, or a patch/base-
- method which attempts to remove a of the patch, the semaphore will
- nicely lock up your code. Secondly, the traversal through the
- patch chain is guaranteed to be serial. This means that even
- if you call DoShadowAsync(), the method will actually be preparsed,
- sent to the base-method's mtag_procObject asynchronously, and only
- then will the patch-chain invocation will occur. This allows
- behaviour which depends on serial traversal (like the
- FLAG_CHECK_CONTINUE) to continue to operate correctly, even though
- each patch is actually invoked asynchronously to the invoker.
-
- Third, while patches are invoked from high priority to low priority,
- the actual return value will be the return value from the -lowest-
- priority patch that ended up returning a valid return value. Fourth,
- the method that is being patched must ALREADY exist within the class
- that's being patched -- the only way to add new methods is to modify
- the superclass hierarchy -- this will be covered at the end of the
- PATCHER_CLASS description. Fifth, there is a speed penalty to using
- patch-chains -- do not expect them to ever be very speedy. And last,
- but certainly not least, because DSM() controls the patch-chain
- traversal, there is no way to modify the arguments as they travel
- from patch to patch. Please be careful and act accordingly.
-
-
- The following code demonstrates the PATCHER_CLASS in action:
-
- /*
- * The patches
- */
- extern double PreTestMethod(METHOD_ARGS);
- extern long PostTestMethod(METHOD_ARGS);
-
- METHOD_TAG preMethod =
- {
- "Method TEST",
- NULL, NULL,
- INVOKE_SYNC,
- METH_FLAG_CHECK_CONTINUE |
- METH_FLAG_CLASS |
- METH_FLAG_NO_RTRN,
- 1, /* The priority */
- (METHODFUNCTYPE)PreTestMethod,
- NULL
- };
-
- METHOD_TAG postMethod =
- {
- "Method TEST",
- NULL, NULL,
- INVOKE_SYNC,
- METH_FLAG_OBJECT |
- METH_FLAG_NO_RTRN,
- -1,
- (METHODFUNCTYPE)PostTestMethod,
- NULL
- };
-
- /*
- * Add pre and post patches to the dosClass.
- * Note that the "Method TEST" method MUST ALREADY exist
- * in the dosClass definition.
- */
- preMethod.mtag_procObject = dosTaskClass;
- preMethod.mtag_defnObject = CurrentProcess();
-
- preObject = CreateInstance(NULL, PATCHER_CLASS, META_CLASS,
- &preMethod,
- dosClass,
- METHOD_END
-
- postMethod.mtag_procObject = dosTask;
- postMethod.mtag_defnObject = CurrentProcess();
-
- postObject = CreateInstance(NULL, PATCHER_CLASS, META_CLASS,
- &postMethod,
- dosClass,
- METHOD_END);
-
-
- /*
- * The method has now been patched!
- * Now, when you call the method, the preMethod is invoked in an
- * instantiated dosTaskClass-process; then the regular method is
- * invoked; then the postMethod is invoked synchronously in the
- * dosTask process.
- */
- .
- .
- .
- /*
- * Remove the patches.
- * You could have added this to the process with the
- * AddAutoResource() call, in which case you would not
- * need to call RemoveObject()....
- */
- RemoveObject(preObject);
- RemoveObject(postObject);
-
-
-
- /*
- * The patcher methods.
- *
- * The PreTestMethod() uses a SAS/C 5.0 side-effect where
- * a double is returned in d0 and d1.
- * Whether the patch-chain continues or not alternates
- * between TRUE and FALSE.
- */
- double PreTestMethod(METHOD_ARGS)
- {
- static int __far myLocalVar = 0; /* YES -- __far is
- required! */
- union {
- double tempD;
- ULONG tempV[2];
- } retval;
- BPTR oldoutput;
-
- oldoutput = SelectOutput(Open("CONSOLE:", MODE_OLDFILE));
-
- VPrintf("Pretest called in <%s> task.\n",
- (ULONG *)&(FindTask(NULL)->tc_Node.ln_Name));
-
- if (!(retval.tempV[1] = (myLocalVar++ & 1)))
- VPrintf("PreTest will block this call:\n", NULL);
-
- oldoutput = SelectOutput(oldoutput);
- Close(oldoutput);
- retval.tempV[0] = 0; /* This number is NOT a valid return!
- * Note the METH_FLAG_NO_RTRN
- */
- return retval.tempD;
- }
-
- /*
- * Note that the "return 200" is NOT a valid return as the
- * METHOD_TAG has the METH_FLAG_NO_RTRN flag set.
- *
- * As the preMethod and the postMethod never return a
- * valid return value, the return value of the base-method
- * is returned, -if- the preMethod doesn't return a FALSE (which
- * would end the patch-chain, and therefore prevent the base-
- * method from being called in the first place. In that case,
- * the DSM() [DoShadow(), et. al.] returns zero.
- */
- long PostTestMethod(METHOD_ARGS)
- {
- VPrintf("Post test called\n", NULL);
- return 200;
- }
-
-
- While PATCHER_CLASS allows you to patch existing methods, it does not
- allow you to add additional methods. There is a way to add methods
- to a class, unfortunately, there is no way to later -remove- those
- methods. The way in which this occurs is to change the superclass
- hierarchy so that the class that you want the method to be added to
- is changed to be a subclass of a class that is created with the
- method you desire.
-
- In otherwords, if you wish to add a method to the PROCESS_CLASS,
- you create an intermediate class with the additional method, then
- point the superclass of the PROCESS_CLASS to the intermediate class.
- It is, of course, required that the intermediate class be a subclass
- of the superclass of the "PROCESS_CLASS", or, the class your adding
- the method to. You can use the SHADOW library function call --
- InsertSuperClass(), or the METH_SUPER method call as demonstrated
- below:
-
- /*
- * Creates a new superclass of a subclass.
- * Adds the methods specific in the my_new_method structure.
- */
- procClass = FindShadowClass(PROCESS_CLASS, META_CLASS);
- newClass = DoShadow(procClass, NULL, METH_SUPER,
- MY_INTERMEDIATE_CLASS,
- NULL,
- my_new_attributes,
- my_new_methods,
- METHOD_END);
- DropObject(procClass);
-
- .
- .
- .
-
- RemoveObject(newClass);
-
-
- This example is taken directly from the ShadowLibraryMethods.doc,
- where you can find more information about the METH_SUPER method. In
- addition, you can find out more about adding new methods by reading
- the InsertSuperClass() method.
-
- Obviously, the preferred manner of adding methods to classes is to
- -subclass- them and create your own classes. However, PATCHER_CLASS
- exists to provide as much flexibility and extensibility as possible.
-
-
- DIRECTOR CLASS
-
- DIRECTOR_CLASS expands upon the notion of extensibility by providing
- notification for important system and program state changes. For
- instance, you can receive notification whenever a class is added
- or removed from the system list, or when a -particular- class is
- added or removed from the system. In the gui I've been
- experimenting with, the ATTR_GUICHILDREN attribute which is a
- "watched" binary tree that contains a GUI-object's children,
- generates notification whenever any children are removed or
- added to the system.
-
- In addition to being able to watch a particular GUI-object, you can
- watch all the GUI-object's of any particular class (and associated
- subclasses) that you'd like. It is entirely feasible to imagine
- receiving notification whenver any program added any GUI-object to
- the child tree of any other GUI-object.
-
- DIRECTOR_CLASS objects handle the request for notification of these
- system and program state changes. These state changes occur in
- special kinds of "state" or "watched" variables. You can create a
- WatchedVariable in the following manner:
-
- void *nullPtr = NULL;
- struct WatchedVariable wv = {NULL, &nullPtr, initial_value};
-
- The &wv can then be used when establishing a connection in
- the DIRECTOR_CLASS. However, as SHADOW is mainly an object-
- oriented system (or, rather, it seems to have grown into being one),
- it is more frequent to find yourself establishing a director-object
- connection to a watched variable inside of a class.
-
- A watched variable inside of a class is created in a very special
- way that allows you not only to watch a single object, but all the
- objects that are instances of a particular class (and instances of
- all classes that are subclasses of that particular class).
-
- Here is an example attribute definition:
-
- ATTRIBUTE_TAG guiAttrs[] =
- {
- {
- ATTR_GUICHILDREN, FLAG_ATTR_WATCHED |
- SHADOW_TREE,
- NULL
- },
- TAG_END
- }
-
- This attribute creates a watched variable that allows you to use
- the attribute as an AVL tree. Although, instead of calling the
- regular AddTreeNode() and/or RemoveTreeStringNode(), you call the
- AddWatchedTreeNode() and/or RemoveWatchedTreeStringNode(). As a
- director watching this attribute, you can get notification about
- when a node is added or removed from the tree (you can also
- receive notification when some other director establishes or
- terminates a connection to any watched variable, but that gets
- just a little ridiculous...).
-
- There are three basic types of watched variables. One is the
- long-word type, another is the AVL tree type which is shown
- above, and the last is the singly-linked list type which I
- won't bother to cover in the examples. These three types
- are denoted by three different flags located in <shadow/watcher.h>:
- SHADOW_VALUE
- SHADOW_TREE
- SHADOW_LIST
- Use these flags in the ATTRIBUTE_TAG value when creating a
- watched variable in an object.
-
- Watching a watched variable is a two-step process. First you
- need to create an instance of a director class. There are several
- things to setup at this phase -- the name of the director
- object, the object that will receive the notification and the
- selector that will be invoked on that object, the type of events
- you want to receive notification about, the type of watcher, and
- the default resource tracking cleanup algorithms that will be
- used when the director's connections are terminated and the
- director is removed from the system.
-
- Second, you need to extablish a connection with a particular watched
- variable/attribute or class of watched attributes. Unlike method
- patches which are can only be installed in a single class at a time,
- director objects can establish themselves in any number of watched
- variables -- allowing you to save, perhaps, a bit of memory in the
- process.... The ESTABLISH method only takes arguments which
- defines the watched variable and returns a handle to the connection
- which should either be sent to DropObject(), or used in any
- explicit call to the METH_DIRECTOR_TERMINATE method.
-
- Setting up a director object is less complex than method patches, but
- nevertheless there are a few flags you should know about [the
- following flags are located in <shadow/watcher.h>]:
-
- /*
- * Match either.
- */
- W_CHANGE_ZERO
- W_CHANGE_NON_ZERO
- W_CHANGE_VALUE
-
- /*
- * Match exactly
- */
- W_NODE
- W_WATCH_CHANGE
- W_REMOVE
- W_INSERT
-
- /*
- * Control matching.
- */
- W_MATCH
- W_MATCH_VALUE W_MATCH
- W_SECOND
-
- W_MATCH_FIRST W_MATCH
- W_MATCH_SECOND (W_SECOND | W_MATCH)
-
- W_INSERT_NODE (W_NODE | W_INSERT)
- W_REMOVE_NODE (W_NODE | W_REMOVE)
-
- W_INSERT_WATCHER (W_WATCH_CHANGE | W_INSERT)
- /* Not valid during... */
- W_REMOVE_WATCHER (W_WATCH_CHANGE | W_REMOVE)
- /* ... INIT[]/DESTROY[] */
-
- The exact meaning of all of those flags is covered in detail under
- the METH_DIRECTOR_NOTIFY method description in ShadowMethods.doc.
- In general, these flags describe which state-change events the
- director object is interested in getting notification about.
-
- /*
- * for use in passing to METH_INIT....
- */
- W_FLAG_AUTOBREAK
- W_FLAG_AUTOREMOVE
-
-
- Do -not- confuse these with the W_AUTOBREAK and W_AUTOREMOVE flags
- which are NOT to be passed into the METH_INIT function!
- The W_FLAG_AUTOBREAK informs the director object that when the
- director is sent a METH_REMOVE method, it should terminate all of
- its outstanding connections.
- The W_FLAG_AUTOREMOVE informs the director object that when the
- last connection is terminated, the director should automatically
- be removed from the system.
- By default, I suggest you use both flags unless you think of some
- -very- good reason not to.
-
- W_OBJECT
- W_CLASS
-
- These two flags specify which type of director you wish to create --
- one to watch an entire class, or one to watch just a specific
- object or watched variable.
-
-
- /*
- * As flag to TERMINATE METHOD.
- */
- W_HANDLE
- W_SORT
-
- There are three ways to TERMINATE a connection. The first is to
- create the director with the W_FLAG_AUTOBREAK flag set and then
- send a METH_REMOVE method (ie. RemoveObject()) to the director.
- This will terminate all connections. The second is to specify,
- using the handle returned to you by the ESTABLISH call, a specific
- connection be TERMINATEd. This is the W_HANDLE case, and if left
- unspecified, the default manner is W_HANDLE. The third case is to
- specify an object that the director was watching and TERMINATE any
- connection in that object -- however, you are not guaranteed
- -which- connection is terminated, if you are watching more than one
- attribute, or one attribute more than once, or some combination
- thereof. This third possibility is usually used by the system,
- rather than the external programmer -- there are very arcane
- reasons for why this needs to exist....
-
-
- There are two different types of directors -- class-watchers and
- object-watchers. Herein we demonstrate both:
-
- /*
- * Create a director object.
- * Specify the director's name as "WatchWindows".
- * Specify the notification be invoked on "object" with the
- * METH_TELL_ME method.
- * Specify interest in addition or removal of nodes from a
- * complex watched variable like SHADOW_TREE or SHADOW_LIST
- * Specify a class watcher
- * Specify the autoremove and autobreak features.
- */
- GlobalDirector = CreateInstance(NULL,
- DIRECTOR_CLASS,
- META_CLASS,
- "WatchWindows",
- object,
- METH_TELL_ME,
- W_INSERT_NODE | W_REMOVE |
- W_CLASS |
- W_FLAG_AUTOBREAK |
- W_FLAG_AUTOREMOVE,
- METHOD_END);
-
- /*
- * Let's watch for additions and removal of children to any
- * object of the WINDOW_CLASS, or any instance of any
- * subclass of the WINDOW_CLASS.... The watched-tree
- * attribute that contains the windows' children is
- * called ATTR_GUICHILDREN. It is not specific to just the
- * WINDOW_CLASS, though....
- *
- * Note that we throw away the handle that the ESTABLISH
- * method returns.
- */
- windowClass = FindShadowClass(WINDOW_CLASS);
- DropObject(DoShadow(GlobalDirector,
- NULL,
- METH_DIRECTOR_ESTABLISH,
- ATTR_GUICHILDREN,
- windowClass,
- METHOD_END));
- DropObject(windowClass);
-
-
- .
- .
- .
-
- /*
- * Cleanup!
- * We didnt't add the GlobalDirector to our AutoResource() tree,
- * so we'll have to do an explicit RemoveObject()!
- * Remember, the W_FLAG_AUTOBREAK means that the METH_REMOVE
- * terminates all connections.
- */
-
- RemoveObject(GlobalDirector);
-
-
- The following example will demonstrate a director watching an object,
- or watching a plain watched variable that does not exist within an
- object:
-
- /*
- * Create a director object.
- * Specify the director's name as "Watch Meta".
- * Specify the notification be invoked on "object" with the
- * METH_INFORM_ME method.
- * Specify interest in addition or removal of nodes from a
- * complex watched variable like SHADOW_TREE or SHADOW_LIST
- * Specify an object/variable watcher
- * Specify the autoremove and autobreak features.
- */
- mlo->director = CreateInstance(NULL,
- DIRECTOR_CLASS,
- META_CLASS,
- "Watch Meta",
- object,
- METH_INFORM_ME,
- W_INSERT_NODE | W_REMOVE |
- W_OBJECT << 16 |
- W_FLAG_AUTOBREAK |
- W_FLAG_AUTOREMOVE,
- METHOD_END);
-
- /*
- * If there is a specific 'meta' to watch, let's watch
- * for additions or removals of instances to/from the meta's
- * ATTR_INSTANCETREE. If there is no meta (that is, if meta
- * is NULL), let's watch SHADOW's public Watched Variable
- * which contains all the current metas and watch for a
- * meta to be added or removed.
- *
- * Note that we throw away the handle that the ESTABLISH
- * method returns.
- *
- * Note, we pass in the object and the object's attribute
- * that we want notification about (meta dn ATTR_INSTANCETREE
- * in this case. If the object is NULL, the attribute, which
- * is usually a string, is now assume to point to a watched
- * variable structure instead.
- *
- * Yes, we really want a W_OBJECT watcher to watch a meta.
- * Why? Because we are watching -one- meta, not a whole
- * *class* of metas, which is where we would want to use a
- * W_CLASS type of director. In otherwords, if you wanted to
- * watch the ATTR_INSTANCETREE of a whole class of metas,
- * you'd use a W_CLASS. Here, though, we need a W_OBJECT
- * director object.
- */
-
- DropObject( DoShadow(mlo->director,
- NULL,
- METH_DIRECTOR_ESTABLISH,
- (meta)?ATTR_INSTANCETREE:
- &ShadowBase->sb_metaTree,
- meta,
- METHOD_END));
-
-
- .
- .
- .
-
- /*
- * Cleanup!
- * We didnt't add the mlo->director to our AutoResource() tree,
- * so we'll have to do an explicit RemoveObject()!
- * Remember, the W_FLAG_AUTOBREAK means that the METH_REMOVE
- * terminates all connections.
- */
-
- RemoveObject(mlo->director);
-
-
- For this example, we explore explicit TERMINATion of a director's
- connections.
-
- /*
- * Create a director object.
- * Specify the director's name as "WatchBlockedChildren".
- * Specify the notification be invoked on "object" with the
- * METH_SLAP_ME_ONCE method.
- * Specify interest in addition or removal of nodes from a
- * complex watched variable like SHADOW_TREE or SHADOW_LIST
- * Specify an object/variable watcher
- * Specify the autoremove and autobreak features.
- */
- director = CreateInstance(NULL,
- DIRECTOR_CLASS,
- META_CLASS,
- "WatchBlockedChildren",
- object,
- METH_SLAP_ME_ONCE
- W_INSERT_NODE | W_REMOVE |
- W_OBJECT << 16 |
- W_FLAG_AUTOBREAK |
- W_FLAG_AUTOREMOVE,
- METHOD_END);
-
- /*
- * Establish the connection for the director.
- * Here, we'll watch the ATTR_GUICHILDREN of an object.
- */
- handle1 = DoShadow(director, NULL, METH_DIRECTOR_ESTABLISH,
- ATTR_GUICHILDREN,
- object,
- METHOD_END);
-
- /*
- * Establish another connection.
- * Here, we'll watch the ATTR_INSTANCETREE of WINDOW_CLASS.
- * Note that we aren't interested in the INSTANCE_TREE
- * attribute for -all- classes, just for the on particular
- * -class-instance- -- WINDOW_CLASS, which is why we want
- * a W_OBJECT watcher as opposed to a W_CLASS watcher.
- */
- windows = FindShadowClass(WINDOW_CLASS);
- handle2 = DoShadow(director, NULL, METH_DIRECTOR_ESTABLISH,
- ATTR_INSTANCETREE,
- windows,
- METHOD_END);
- DropObject(windows);
-
- .
- .
- .
-
- /*
- * Terminate the first connection.
- */
- DoShadow(director, NULL, METH_DIRECTOR_TERMINATE, handle1,
- METHOD_END);
-
- .
- .
- .
- /*
- * Terminate the second connection.
- */
- DoShadow(director, NULL, METH_DIRECTOR_TERMINATE, handle2,
- METHOD_END);
-
- .
- .
- .
- /*
- * Cleanup!
- *
- * Note, because we specified the W_AUTOREMOVE feature, and
- * because we've closed all of the connections, the director
- * has already been removed. Therefore, simply DropObject()
- * it.
- */
- DropObject(director);
-
-
- This code is taken directly from the Browser included in the SHADOW
- distribution. Browser uses W_OBJECT watchers to maintain a current
- list of the available classes, objects, and number of patches on a
- method. If you add a class while the Browser is showing a list of
- the classes, that list is updated automatically, without the active
- knowledge of the program creating the class!
-
-
-
- PROCESS CLASS
-
- Process classes offer several hurdles that are not encountered
- with any other objects. First, the initialization of a process is
- primarily a two-stage procedure -- the creation of an AmigaDOS
- process, and then the initialization of all of the ports, libraries,
- etc. that that particular task requires. Note that the second
- stage -must- occur within the created task, whereas the first
- stage -cannot- occur within that task (it wouldn't be around at
- that point). Second, the cleanup of processes (ie: the removal
- of ports and libraries bases, etc.) must occur within the task
- itself, but cannot actually occur until all references to the
- process object are eliminated. On the otherhand, many of the
- resources added to a process via the AddAutoResource() call
- reference the process object (usually indirectly, through
- an mtag_defnObject field in a method within a class, for instance).
- Therefore, the METH_REMOVE must clear all of the automatic
- resources, but the METH_DESTROY is responsible for actually
- closing down the ports, allocated signals, and library
- bases.
-
- Additionally, process creation must take into account that a program
- launched via the CLI or the Workbench does not have a SHADOW
- process object associated with it, and therefore does not contain
- the requisite ports and messages to deal with the sending and
- receiving of SHADOW methods. There must be a way to associate
- an already existing process with a SHADOW process object after
- the process creation (note how similar this requirement is to
- the two-stage startup procedure described in the above paragraph).
-
- Finaly, processes should provide an example on how to properly deal
- with SHADOW messages by providing a default message handler which
- handles all of the various SHADOW methods and returns when the
- process gets a ^C (a ^C is automatically sent to the process by
- the system under certain shut-down conditions as well).
-
-
- Process and Program Startup and initialization
-
- The initialization of a process object is separated into two separate
- parts -- a METH_INIT and a METH_PROC_ASSOCIATE. The METH_INIT
- takes several parameters:
- The name of the process
- One of two parameters that prevents a parent process from
- exitting before its children
- A taglist that is passed to the CreateNewPoc() AmigaDOS call
- A ProcessInitFrame -- for the METH_PROC_ASSOCIATE call
- A ProcessHandlerFrame -- for the METH_PROC_HANDLER call
-
- Let's take them one at a time, ignoring the process name argument,
- as its function is obvious.
-
- Let us say that you start a thread on one of your processes. You
- definitely do not want your parent process going away before your
- child threads have all disappeared -- there are two ways to prevent
- this. First, you can pass an Exec semaphore pointer to the
- INIT code. The thread that is created will grab a SHARED lock on
- this semaphore between the time that the METH_INIT is called,
- and the time when the METH_INIT returns (the magic involved therein
- is unimportant).
-
- Therefore, before your parent process disappears, it should attempt
- to grab an exclusive lock on this semaphore. You can accomplish
- this by passing the semaphore into the RemoveCurrentProgram()
- call that all SHADOW programs call in their cleanup code.
-
- A much easier way (and the preferred SHADOW V way to deal with these
- issues) is to pass the parent's own process object into the the
- METH_INIT call. SHADOW will then resource track the parent process
- within the child thread. The parent process will be dropped only
- after the program returns from the _HANDLER routine, thereby
- preventing the parent process from disappearing before its children.
-
- The taglist is a taglist that is passed to the CreateNewProc()
- library call under AmigaDOS. Several tags are reserved for
- exclusive use by SHADOW. They are:
- NP_Name
- NP_ExitData
- NP_Entry
-
- NP_Name is set to the name passed into the INIT call.
- NP_ExitData is used internally.
- NP_Entry is used internally. You should use the ProcessHandlerFrame
- to set your entrypoint -- this will be a -method- entry-point....
-
- The ProcessInitFrame is a METHOD_END-terminated array of the
- arguments that will be used to finish the creation of the process.
- If NULL, the internal definition of the arguments that
- METH_PROC_ASSOCIATE takes is used. You may override which method is
- called to complete the process initialization by specifying a non-
- NULL field for the pif_methodID field. If NULL, the method is
- assumed to be METH_PROC_ASSOCIATE. In addition, you may add any
- number of arguments to the end of the InitFrame, though the
- final arguments should all be taken by the destination method.
-
- The following is the internal ProcessInitFrame that is used by
- SHADOW. It resides in <shadow/process.h>:
-
- struct ProcessInitFrame {
- struct CoreObject *pif_object;
- struct Meta *pif_class;
- char *pif_methodID;
- char *pif_procName;
- void *pif_args[1]; /*
- * Other arguments,
- * terminated by METHOD_END
- */
- };
-
-
- The METH_INIT code fills in the object, and the class, and changes the
- methodID as required. If the ProcessInitFrame is passed in, the
- pif_procName is not touched, and the rest of the arguments are
- assumed to be valid and terminated by a METHOD_END. The entire
- array is passed to DSM() which returns a message encapsulating
- the appropriate method call. The actual invocation of this
- method will occur once the process starts up.
-
- The ProcessHandlerFrame is the entry point (method) into the thread.
- By default, this is the METH_PROC_HANDLER method which takes no
- arguments. As with the ProcessInitFrame structure, the actual
- method can be overrided by passing in your own methodID in the
- ProcessHandlerFrame. A NULL methodID field in a passed
- ProcessHandlerFrame is assumed to mean METH_PROC_HANDLER.
-
- The following is the internal ProcessHandlerFrame that is used by
- SHADOW. It also resides in <shadow/process.h>:
-
- struct ProcessHandlerFrame {
- struct CoreObject *phf_object;
- struct Meta *phf_class;
- char *phf_methodID;
- void *phf_args[1]; /*
- * Other arguments,
- * terminated by METHOD_END
- */
- };
-
- Once again, either the default ProcessHandlerFrame, or one that you
- provide, is passed into DSM() and a message is returned. During
- the internal startup code of the thread, the ProcessHandlerFrame's
- message is invoked subsequent to the ProcessInitFrame's message.
-
- Once both of these messages are created, the actual AmigaDOS process
- is created. The METH_INIT will not return yet, though, the
- METH_PROC_ASSOCIATE (or the method specified in the ProcessInitFrame)
- is allowed to run first. If this method returns 0L, then the
- initialization is assumed to have failed, the thread shuts down, and
- the METH_INIT returns a NULL process object. If this method
- returns a non-zero value, the parent process is signalled to
- wakeup, and the thread calls the METH_PROC_HANDLER method (or
- the method specified in the ProcessHandlerFrame).
-
- When the parent process gets woken up, it attempts to discover
- whether or not the process ended up working or not, and if it did,
- the process object is returned, otherwise NULL is returned. In
- addition, if your process is created, the tag entries are
- TAG_IGNORE'd -- this way you know whether you need to free any
- resources the tags might have pointed to (like file pointers for
- stdin and stdout). Note that these fields are cleared whether or not
- the ProcessInitFrame method succeeded, the important point is
- whether or not the process was created. After all, the process
- cleanup code that DOS runs cleans up stdin/out, etc....
-
-
- Much has been said about METH_PROC_ASSOCIATE -- that there is a
- method which is called (by default) by the new thread when the
- process is created, but very little has been said as to what it
- actually is supposed to do, and, no mention of hooking-up already
- existing processes with SHADOW process objects has been made.
-
- The METH_PROC_ASSOCIATE is the second stage of the two-stage process-
- creation procedure. It is responsible for allocating ports for the
- new process object, opening up any libraries you might want opened,
- creating any additional ports (like an IDCMP port, for example)
- that you might want -- generally, any resource allocation that a
- process has to do up-front. In addition, once all of this is
- successfully done, the METH_INIT method is forwarded from the
- PROCESS_CLASS to the ROOT_CLASS. The process object, therefore, is
- not on any system lists until the METH_PROC_ASSOCIATE is called.
- Also, for your information, the process object is stored in the
- tc_UserData field of the process -- MAKE SURE YOU DON'T TOUCH IT!
-
- In addition, when a program starts, you are already given a process,
- but no SHADOW object exists for this process. Creating a process
- object and calling METH_PROC_ASSOCIATE on it allows you to attach
- the AmigaDOS process to a SHADOW object, even after AmigaDOS has
- actually created the process.
-
- Startup in your program might look like this:
-
- ShadowBase = OpenLibrary("shadow.library", 5);
- if (!ShadowBase)
- abort_and_cleanse();
-
- procClass = FindShadowClass(PROCESS_CLASS);
- procObject = DoShadow(procClass, NULL, METH_CREATE, METHOD_END);
- DropObject(procClass);
-
- /*
- * an object has been created, now initialize it!
- * Note: procObject is swallowed by this call -- consider it
- * never to have really existed as an object....
- */
- if (!DoShadow(procObject, NULL, METH_PROC_ASSOCIATE,
- "My program",
- METHOD_END))
- abort_and_cleanse();
-
- /*
- * Okay, our process is running!
- */
-
- While this is useful if you want to start with something other than
- a PROCESS_CLASS object, the default PROCESS_CLASS is very useful, and
- a library call can be used instead of this code, which does
- essentially the same thing:
-
- ShadowBase = OpenLibrary("shadow.library", 5);
- if (!ShadowBase)
- abort_and_cleanse();
- if (!InitOOProgram("My program"))
- abort_and_cleanse();
-
- Note that we do NOT call CreateInstance() for our process object.
- That, of course, is because we do not want a -new- process, just a
- new SHADOW object to attach to an already existing process....
-
-
- Along with METH_PROC_ASSOCIATE, you've also come across
- METH_PROC_HANDLER. As a default, this is merely the main event loop.
- The main event loop returns when a ^C is sent to the process, however
- messages may continue to get handled even after the ^C is sent so
- that the process can get a shot at deleting its resources....
-
- Consider, then, the following complex example:
- Process A's handler first sends an asynchronous method to
- process A to, say, draw a random circle. It then invokes the
- superclass METH_PROC_HANDLER for default message handling.
- Within the draw circle routine is a method invocation that,
- again, invokes an asynchronous method to draw another random
- circle. Obviously, this executes ad-nauseum. Now, the
- default handler code is careful about buffering the message
- lists, and therefore allowing the ^C to get processed to start
- the quit sequence of the program, however, because the program
- always has a method waiting for it, it can never shutdown.
- Therefore, your custom handler should also set a global
- variable when the system METH_PROC_HANDLER returns, and the
- circle drawing should note this flag and NOT send another
- asynchronous method when it is sent....
-
- Fortunately, however, SHADOW already provides such a flag.
- In particular, SHADOW_PROC_FLAGS_REMOVED in ATTR_SHADOWPROCESS'
- sp_flags field is set when the process has returned from the
- HANDLER (or ParseHandlerFrame) routine. Note that this is not
- set within the METH_PROC_HANDLER method, but is set after the
- routine has exitted by the internal processStartup code.... Thus,
- if you subclass the METH_PROC_HANDLER routine, the flag may or may
- not be set when the superclass' METH_PROC_HANDLER method returns to
- you. Indeed, as noted, you may even be overriding which method
- call is actually made (via the ParseHandlerFrame). It is when this
- method returns that the flag is actually set.
-
-
- Two more examples for initialization:
-
- child = CreateInstance(NULL, PROCESS_CLASS,
- META_CLASS,
- METH_INIT,
- "New Process",
- CurrentProcess(),
- /* Parent process object */
- NULL,
- /* A NULL semaphore --
- strictly speaking, you could
- have gotten away with putting
- the METHOD_END here, it is
- included to remind you that
- the parent object is separate from
- the semaphore field */
- METHOD_END);
-
- /*
- * Yep! A new process has started up (assuming child not NULL).
- * We may now use this child in a call to SetupMethodTags() to
- * allow some of our methods to run in the child process.
- */
-
- success = AddAutoResource(NULL, child, "New Process");
- DropObject(child)
- .
- .
- .
-
- /*
- * cleanup!
- */
- RemoveCurrentProgram(NULL);
- CloseLibrary(ShadowBase);
- .
- .
- .
-
-
- This example shows off how to use the ParseFrames correctly:
- Let's say that you have written a METH_PROC_HANDLER for your
- program's processes in the following fashion:
-
- ARGUMENT_TAG REF_HandleMessages[] =
- {
- ARG_STR,
- ARG_TAGL(sizeof(struct MethodTag)),
- ARG_TAGL(sizeof(struct AttributeTag)),
- RET_NORMAL
- };
- void HandleProcMessages(METHOD_ARGS, char *baseClassName,
- char *newModuleClassName,
- struct MethodTag *methods,
- struct AttributeTag *attrs)
- {
- geta4(); /* Yep, don't forget this! */
-
- /*
- * Allocate a module class for this process.
- */
- AddAutoResource(NULL, CreateSubClass(NULL,
- baseClassName,
- META_CLASS,
- newModuleClassName,
- NULL,
- methods,
- attrs,
- METHOD_END),
- newModuleClassName);
- DoSuperShadow(object, class, MethodID, METHOD_END);
- }
-
- Now, for your own program's process startup, you would naturally use
- something like the following:
-
- /*
- * Create the process object and attach to the program.
- */
- procClass = FindShadow(MY_PROCESS_CLASS);
- success = DoShadow( DoShadow(procClass,
- NULL,
- METH_CREATE,
- METHOD_END),
- NULL,
- METH_PROC_ASSOCIATE,
- NEW_PROCESS_NAME,
- METHOD_END);
- DropObject(procClass);
-
- if (success)
- {
- /*
- * Startup the process and handle all incoming
- * methods. Also, create my module's class.
- */
- DoShadow(CurrentProcess(), NULL, METH_PROC_HANDLER,
- BASE_MODULE_CLASS,
- MY_NEW_MODULE_CLASS,
- myMethods,
- myAttrs,
- METHOD_END);
- RemoveCurrentProgram(NULL);
- }
-
- /*
- * Get out!
- */
- CloseLibrary("shadow.library", 5);
- exit();
-
- However, this does not solve a bigger problem -- how to create -new-
- processes that are not related to an already existing program-process.
- Obviously, the normal METH_INIT will not work, as the normal
- METH_PROC_HANDLER is called without any arguments. We can do one of
- two things. The first crack is to call the METH_INIT in the following
- clumsy fashion:
-
- struct MyHandlerFrame {
- OBJECT object;
- META class;
- char *MethodID;
- char *baseClass;
- char *newClass;
- METHOD_TAG *methods;
- ATTRIBUTE_TAG *attrs;
- void *end;
- } mhf;
-
- mhf.MethodID = NULL;
- mhf.baseClass = BASE_MODULE_CLASS;
- mhf.newClass = MY_NEW_MODULE_CLASS_2;
- mhf.methods = myMethods_2;
- mhf.attrs = myAttrs_2;
- mhf.end = METHOD_END;
-
- child = CreateInstance(NULL,
- MY_PROCESS_CLASS,
- META_CLASS,
- METH_INIT,
- "New Process #2",
- CurrentProcess(),/* Parent process */
- NULL, /* No semaphore */
- NULL, /* no tags */
- NULL, /* No InitFrame */
- &mhf
-
- METHOD_END);
-
- This is clumsy because each time you want to create a new process,
- you also have to setup the MyHandlerFrame. In addition, because
- the MyHandlerFrame structure has no definitive -length- to it, the
- METH_INIT cannot be called asynchronously. A much better way is to
- redefine the way you call the METH_INIT for the process as follows:
-
- /*
- * ProcessClass' METH_INIT
- *
- * NULL if error, else returns the new class-object.
- *
- */
- struct ArgumentTag REF_InitProcObject[] = {
- ARG_STR,
- ARG_STR,
- ARG_STR,
- ARG_TAGL(sizeof(struct MethodTag)),
- ARG_TAGL(sizeof(struct AttributeTag)),
- RET_OBJ
- };
-
- OBJECT InitProcObject(METHOD_ARGS, char *processName,
- char *baseClassName,
- char *newModuleClassName,
- struct MethodTag *methods,
- struct AttributeTag *attrs)
- {
- struct MyHandlerFrame {
- OBJECT object;
- META class;
- char *MethodID;
- char *baseClass;
- char *newClass;
- METHOD_TAG *methods;
- ATTRIBUTE_TAG *attrs;
- void *end;
- } mhf;
-
- mhf.MethodID = NULL; /* default! */
- mhf.baseClass = baseClassName;
- mhf.newClass = newModuleClassName;
- mhf.methods = methods;
- mhf.attrs = attrs;
- mhf.end = METHOD_END;
-
- /*
- * This method always runs as a callback!
- * So we don't have to worry about resource tracking
- * the variable length HandlerFrame.
- */
- return DoSuperShadow(object, class, MethodID,
- processName,
- CurrentProcess(),
- NULL, NULL, NULL,
- &mhf,
- METHOD_END);
- }
-
- This is much cleaner, as now to create a new process, all you need to
- do is the following:
-
- child = CreateInstance(NULL, META_CLASS, MY_PROCESS_CLASS,
- "New Process #3",
- BASE_MODULE_CLASS,
- MY_NEW_MODULE_CLASS_3,
- myMethods_3,
- myAttrs_3,
- METHOD_END);
-
- This, obviously, looks a lot better, it hides the common code
- in one place, allowing you to easily create new modules that use the
- same master process code without duplicating all of the
- initialization/structure setups....
-
-
- Process and Program Shutdown:
-
- The destruction of a process has similar problems as the
- initialization of processes. Remember that the destruction of an
- object -already- occurs in two stages -- the REMOVE and then the
- DESTROY. Unfortunately, the METH_DESTROY method really needs to be
- called as a function, there is, therefore, no guaranteed way to get
- the METH_DESTROY to occur within the process. The destroy process,
- therefore, has been broken up into two pieces:
- METH_DESTROY -- this sets a DESTROY bit in the ShadowProcess
- structure and then signals the task with a
- ^C
- METH_PROC_DISASSOCIATE -- this destroys the ports that were
- allocated by SHADOW, and you should
- subclass this to destroy/close and
- resources you created/opened in the
- ASSOCIATE method. When this method
- returns the process' name has also
- been freed.... The object's
- destruction is completed with magic
- that will not be discussed here, but
- it is -not- done by the
- METH_DISASSOCIATE method.
-
- Neither of these methods should be called by you, neither do they
- take any arguments. You may, however, subclass either one of
- them. For the most part, anything that you would have normally
- put into the the METH_DESTROY you should put into the
- METH_DISASSOCIATE instead....
- By the way, both of these methods methods call the automatic
- cleanup procedure, RemoveResources(). The DISASSOCIATE method
- calls the RemoveResources with the boolean argument set to TRUE,
- whereas both the METH_REMOVE and the METH_DESTROY call
- RemoveResources() with the boolean argument set to FALSE.
-
- However, process destruction is a two step process, and the first
- step is the METH_REMOVE signal. The METH_REMOVE sets a REMOVE
- bit in the ShadowProcess flags field (to prevent duplicate
- METH_REMOVE methods) and calls the RemoveResources() call.
-
- Process shutdown of a program that has been started by the
- InitOOProgram() call, or the CREATE-ASSOCIATE pair of calls,
- needs to be shutdown with the RemoveCurrentProgram() call. This
- call takes as an argument the optional semaphore that you
- started all of the threads with. As stated before, the preferred
- manner of starting up is to use the parent process and not a
- global program semaphore -- but the option exists for you to
- consider. The RemoveCurrentProgram() will not return until it
- is safe for your program to exit.
-
-
- METH_FLAG_OBJECT_AS_DEST
-
- There is a specific method flag to deal with process objects --
- specifically the METH_REMOVE method. It is completely reasonable
- to wish that a method for a process actually run in that process.
- This is the reason for the METH_FLAG_OBJECT_AS_DEST flag for the
- mtag_flags field of the METHOD_TAG structure. Specifying the
- METH_FLAG_OBJECT_AS_DEST instead of METH_FLAG_OBJECT or
- METH_CLASS, makes the destination of the method the same as the
- object the method is being invoked on. In short, invoking a
- method on a process object, where the method is set with the
- METH_OBJECT_AS_DEST flag, forces the method to be run in that
- process object.
-
-
- Initialization Calls
-
- To recap from the PROCESS_CLASS discussion above, a program is
- initialized and removed in the following fashion under SHADOW:
-
- if (ShadowBase = OpenLibrary("shadow.library", 5))
- {
- if (InitOOProgram(NEW_PROCESS_NAME))
- {
- /*
- * Do what you like!
- * Probably a call to HandleMessages()
- * HandleMessages() is a macro for:
- * DoShadow(CurrentProcess(), NULL, METH_PROC_HANDLER,
- * METHOD_END);
- */
-
-
- /*
- * Cleanup!
- */
- RemoveCurrentProgram(NULL);
- }
- CloseLibrary(ShadowBase);
- }
-
-
-
- CONCLUSION
-
-
-
- We have finally come to the end of the "Introduction" to SHADOW.
- We have come quite far during the course of this document.
- Unfortunately, not everything has been covered in the detail
- that it really needs to be. In order to better understand
- SHADOW, the ShadowLibraryMethods.doc, the ShadowLibraryFuncs.doc,
- and the ShadowLibFuncs.doc are highly recommended. The introduction
- to ShadowLibraryMethods.doc is recommended regardless.
-
- In addition, you can browse the basic class descriptions of
- SHADOW using the browser. In addition, the source to browser,
- though somewhat hacked, is included for your use and perusal.
-
- If you have understood all of this -- congratulations! You've done
- better than I would have! Try and figure out the included sources,
- and contact me with cash for your includes and shadow.lib today!
-
- David C. Navas
- SHADOW
- 7 Avocet Dr. #205
- Redwood Shores, CA 94065
-
- jazz@netcom.com
- BIX: dnavas
-
-